diff options
Diffstat (limited to '')
-rw-r--r-- | src/.cvsignore | 15 | ||||
-rw-r--r-- | src/Makefile.in | 133 | ||||
-rw-r--r-- | src/cgi-wrapper.c | 68 | ||||
-rw-r--r-- | src/common.c | 299 | ||||
-rw-r--r-- | src/common.h | 61 | ||||
-rw-r--r-- | src/mail-wrapper.c | 91 | ||||
-rw-r--r-- | src/vsnprintf.c | 125 |
7 files changed, 792 insertions, 0 deletions
diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 00000000..34054773 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,15 @@ +Makefile +admin +admindb +archives +confirm +create +edithtml +handle_opts +listinfo +mailman +options +private +rmlist +roster +subscribe diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 00000000..05e144f2 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,133 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# NOTE: Makefile.in is converted into Makefile by the configure script +# in the parent directory. Once configure has run, you can recreate +# the Makefile by running just config.status. + +# Variables set by configure + +prefix= @prefix@ +exec_prefix= @exec_prefix@ +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ + +CC= @CC@ +CHMOD= @CHMOD@ +INSTALL= @INSTALL@ +PYTHON= @PYTHON@ + +DEFS= @DEFS@ +LIBS= @LIBS@ + +# user and group ids/names +MAIL_GROUP= @MAIL_GROUP@ +CGI_GROUP= @CGI_GROUP@ +MAILMAN_USER= @MAILMAN_USER@ + +# Customizable but not set by configure +OPT= @OPT@ +CFLAGS= @CFLAGS@ $(OPT) $(DEFS) $(LIBS) +CGIDIR= $(exec_prefix)/cgi-bin +CGIEXT= @CGIEXT@ +MAILDIR= $(exec_prefix)/mail + +SHELL= /bin/sh + +MAIL_FLAGS= -DMAIL_GROUP="\"$(MAIL_GROUP)\"" + +CGI_FLAGS= -DCGI_GROUP="\"$(CGI_GROUP)\"" + +HELPFUL= -DHELPFUL + +COMMON_FLAGS= -DPREFIX="\"$(prefix)\"" \ + -DPYTHON="\"$(PYTHON)\"" \ + $(HELPFUL) + + +# Modes for directories and executables created by the install +# process. Default to group-writable directories but +# user-only-writable for executables. +DIRMODE= 775 +EXEMODE= 755 +INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) +DIRSETGID= chmod g+s + +# Fixed definitions + +CGI_PROGS= admindb admin confirm create edithtml listinfo options \ + private rmlist roster subscribe + +COMMONOBJS= common.o vsnprintf.o + +MAIL_PROGS= mailman + +#ALIAS_PROGS= addaliases + +SUID_CGI_PROGS= private + +SUID_MAIL_PROGS= + +PROGRAMS= $(CGI_PROGS) $(MAIL_PROGS) $(ALIAS_PROGS) + + +# Rules + +all: $(PROGRAMS) + +mailman: $(srcdir)/mail-wrapper.c $(COMMONOBJS) + $(CC) -I. $(MAIL_FLAGS) $(CFLAGS) $(COMMONOBJS) -o $@ $(srcdir)/mail-wrapper.c + +#addaliases: $(srcdir)/alias-wrapper.c $(COMMONOBJS) +# $(CC) -I. $(ALIAS_FLAGS) $(CFLAGS) -o $@ $(srcdir)/alias-wrapper.c + +$(CGI_PROGS): $(srcdir)/cgi-wrapper.c $(COMMONOBJS) + $(CC) -DSCRIPT="\"$@\"" -I. $(CGI_FLAGS) $(CFLAGS) $(COMMONOBJS) -o $@ $(srcdir)/cgi-wrapper.c + +common.o: $(srcdir)/common.c $(srcdir)/common.h Makefile + $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(srcdir)/common.c + +vsnprintf.o: $(srcdir)/vsnprintf.c Makefile + $(CC) -c -I. $(COMMON_FLAGS) $(CFLAGS) $(srcdir)/vsnprintf.c + +install: all + for f in $(CGI_PROGS); \ + do \ + exe=$(CGIDIR)/$$f$(CGIEXT); \ + $(INSTALL_PROGRAM) $$f $$exe; \ + $(DIRSETGID) $$exe; \ + done + for f in $(MAIL_PROGS); \ + do \ + $(INSTALL_PROGRAM) $$f $(MAILDIR); \ + $(DIRSETGID) $(MAILDIR)/$$f; \ + done + +finish: + -for f in $(SUID_CGI_PROGS); \ + do \ + exe=$(CGIDIR)/$$f$(CGIEXT); \ + chmod u+s $$exe; \ + chown $(MAILMAN_USER) $$exe; \ + done + +clean: + -@rm *.o + -@rm -f $(PROGRAMS) + +distclean: clean + -@rm Makefile diff --git a/src/cgi-wrapper.c b/src/cgi-wrapper.c new file mode 100644 index 00000000..351d4e1e --- /dev/null +++ b/src/cgi-wrapper.c @@ -0,0 +1,68 @@ +/* cgi-wrapper.c --- Generic wrapper that will take info from a environment + * variable, and pass it to two commands. + * + * Copyright (C) 1998,1999,2000,2001,2002 by the 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "common.h" + +/* passed in by configure */ +#define SCRIPTNAME SCRIPT +#define LOG_IDENT "Mailman cgi-wrapper (" SCRIPT ")" + +/* Group name that CGI scripts run as. See your web server's documentation + * for details. + */ +#define LEGAL_PARENT_GROUP CGI_GROUP + +const char* logident = LOG_IDENT; +char* script = SCRIPTNAME; +const char* parentgroup = LEGAL_PARENT_GROUP; + + +int +main(int argc, char** argv, char** env) +{ + int status; + char* fake_argv[3]; + + running_as_cgi = 1; + check_caller(logident, parentgroup); + + /* For these CGI programs, we can ignore argc and argv since they + * don't contain anything useful. `script' will always be the driver + * program and argv will always just contain the name of the real + * script for the driver to import and execute (padded with two dummy + * values in argv[0] and argv[1] that are ignored by run_script(). + */ + fake_argv[0] = NULL; + fake_argv[1] = NULL; + fake_argv[2] = script; + + status = run_script("driver", 3, fake_argv, env); + fatal(logident, status, "%s", strerror(errno)); + return status; +} + + + +/* + * Local Variables: + * c-file-style: "python" + * End: + */ diff --git a/src/common.c b/src/common.c new file mode 100644 index 00000000..cde8064c --- /dev/null +++ b/src/common.c @@ -0,0 +1,299 @@ +/* common.c --- Common routines, constants, etc. Used by all the wrappers. + * + * Copyright (C) 1998,1999,2000,2001,2002 by the 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "common.h" + +/* Passed in by configure. */ +#define SCRIPTDIR PREFIX "/scripts/" /* trailing slash */ +#define MODULEDIR PREFIX /* no trailing slash */ + +const char* scriptdir = SCRIPTDIR; +const char* moduledir = MODULEDIR; +char* python = PYTHON; + +/* Global variable used as a flag */ +int running_as_cgi = 0; + + + +/* Some older systems don't define strerror(). Provide a replacement that is + * good enough for our purposes. + */ +#ifndef HAVE_STRERROR + +extern char *sys_errlist[]; +extern int sys_nerr; + +char* strerror(int errno) +{ + if (errno < 0 || errno >= sys_nerr) { + return "unknown error"; + } + else { + return sys_errlist[errno]; + } +} + +#endif /* ! HAVE_STRERROR */ + + + +/* Report on errors and exit + */ +#define BUFSIZE 1024 + +void +fatal(const char* ident, int exitcode, char* format, ...) +{ +#ifndef HAVE_VSNPRINTF + /* A replacement is provided in vsnprintf.c for ancient systems still + * lacking one in their C library. + */ + int vsnprintf(char*, size_t, const char*, va_list); +#endif /* !HAVE_VSNPRINTF */ + + char log_entry[BUFSIZE]; + + va_list arg_ptr; + va_start(arg_ptr, format); + + vsnprintf(log_entry, BUFSIZE, format, arg_ptr); + va_end(arg_ptr); + +#ifdef HAVE_SYSLOG + /* Write to the console, maillog is often mostly ignored, and root + * should definitely know about any problems. + */ + openlog(ident, LOG_CONS, LOG_MAIL); + syslog(LOG_ERR, "%s\n", log_entry); + closelog(); +#endif /* HAVE_SYSLOG */ + +#ifdef HELPFUL + /* If we're running as a CGI script, we also want to write the log + * file out as HTML, so the admin who is probably trying to debug his + * installation will have a better clue as to what's going on. + * + * Otherwise, print to stderr a short message, hopefully returned to + * the sender by the MTA. + */ + if (running_as_cgi) { + printf("Content-type: text/html\n\n"); + printf("<head>\n"); + printf("<title>Mailman CGI error!!!</title>\n"); + printf("</head><body>\n"); + printf("<h1>Mailman CGI error!!!</h1>\n"); + printf("The Mailman CGI wrapper encountered a fatal error. "); + printf("This entry is being stored in your syslog:"); + printf("\n<pre>\n"); + printf(log_entry); + printf("</pre>\n"); + } + else + fprintf(stderr, "%s\n", log_entry); +#endif /* HELPFUL */ + exit(exitcode); +} + + + +/* Is the parent process allowed to call us? + */ +void +check_caller(const char* ident, const char* parentgroup) +{ + GID_T mygid = getgid(); + struct group *mygroup = getgrgid(mygid); + char* option; + char* server; + char* wrapper; + + if (running_as_cgi) { + option = "--with-cgi-gid"; + server = "web"; + wrapper = "CGI"; + } + else { + option = "--with-mail-gid"; + server = "mail"; + wrapper = "mail"; + } + + if (!mygroup) + fatal(ident, GROUP_NAME_NOT_FOUND, + "Failure to find group name %s. Try adding this group\n" + "to your system, or re-run configure, providing an\n" + "existing group name with the command line option %s.", + parentgroup, option); + + if (strcmp(parentgroup, mygroup->gr_name)) + fatal(ident, GROUP_MISMATCH, + "Group mismatch error. Mailman expected the %s\n" + "wrapper script to be executed as group \"%s\", but\n" + "the system's %s server executed the %s script as\n" + "group \"%s\". Try tweaking the %s server to run the\n" + "script as group \"%s\", or re-run configure, \n" + "providing the command line option `%s=%s'.", + wrapper, parentgroup, server, wrapper, mygroup->gr_name, + server, parentgroup, option, mygroup->gr_name); +} + + + +/* list of environment variables which are removed from the given + * environment. Some may or may not be hand crafted and passed into + * the execv'd environment. + * + * TBD: The logic of this should be inverted. IOW, we should audit the + * Mailman CGI code for those environment variables that are used, and + * specifically white list them, removing all other variables. John Viega + * also suggests imposing a maximum size just in case Python doesn't handle + * them right (which it should because Python strings have no hard limits). + */ +static char* killenvars[] = { + "PYTHONPATH=", + "PYTHONHOME=", + "PATH=", + NULL +}; + + + +/* Run a Python script out of the script directory + * + * args[0] should be the abs path to the Python script to execute + * argv[1:] are other args for the script + * env may or may not contain PYTHONPATH, we'll substitute our own + * + * TBD: third argument env may not be universally portable + */ +int +run_script(const char* script, int argc, char** argv, char** env) +{ + const char envstr[] = "PYTHONPATH="; + const int envlen = strlen(envstr); + + int envcnt = 0; + int i, j, status; + char** newenv; + char** newargv; + + /* We need to set the real gid to the effective gid because there are + * some Linux systems which do not preserve the effective gid across + * popen() calls. This breaks mail delivery unless the ~mailman/data + * directory is chown'd to the uid that runs mail programs, and that + * isn't a viable alternative. + */ +#ifdef HAVE_SETREGID + status = setregid(getegid(), -1); + if (status) + fatal(logident, SETREGID_FAILURE, "%s", strerror(errno)); +#endif /* HAVE_SETREGID */ + + /* We want to tightly control how the CGI scripts get executed. + * For portability and security, the path to the Python executable + * is hard-coded into this C wrapper, rather than encoded in the #! + * line of the script that gets executed. So we invoke those + * scripts by passing the script name on the command line to the + * Python executable. + * + * We also need to hack on the PYTHONPATH environment variable so + * that the path to the installed Mailman modules will show up + * first on sys.path. + * + */ + for (envcnt = 0; env[envcnt]; envcnt++) + ; + + /* okay to be a little too big */ + newenv = (char**)malloc(sizeof(char*) * (envcnt + 2)); + + /* filter out any troublesome environment variables */ + for (i = 0, j = 0; i < envcnt; i++) { + char** k = &killenvars[0]; + int keep = 1; + while (*k) { + if (!strncmp(*k, env[i], strlen(*k))) { + keep = 0; + break; + } + *k++; + } + if (keep) + newenv[j++] = env[i]; + } + + /* Tack on our own version of PYTHONPATH, which should contain only + * the path to the Mailman package modules. + * + * $(PREFIX)/modules + */ + newenv[j] = (char*)malloc(sizeof(char) * ( + strlen(envstr) + + strlen(moduledir) + + 1)); + strcpy(newenv[j], envstr); + strcat(newenv[j], moduledir); + j++; + + newenv[j] = NULL; + + /* Now put together argv. This will contain first the absolute path + * to the Python executable, then the -S option (to speed executable + * start times), then the absolute path to the script, then any + * additional args passed in argv above. + */ + newargv = (char**)malloc(sizeof(char*) * (argc + 3)); + j = 0; + newargv[j++] = python; + newargv[j++] = "-S"; + newargv[j] = (char*)malloc(sizeof(char) * ( + strlen(scriptdir) + + strlen(script) + + 1)); + strcpy(newargv[j], scriptdir); + strcat(newargv[j], script); + + /* now tack on all the rest of the arguments. we can skip argv's + * first two arguments because, for cgi-wrapper there is only argv[0]. + * For mail-wrapper, argv[1] is the mail command (e.g. post, + * mailowner, or mailcmd) and argv[2] is the listname. The mail + * command to execute gets passed in as this function's `script' + * parameter and becomes the argument to the python interpreter. The + * list name therefore should become argv[2] to this process. + * + * TBD: have to make sure this works with alias-wrapper. + */ + for (i=2, j++; i < argc; i++) + newargv[j++] = argv[i]; + + newargv[j] = NULL; + + /* return always means failure */ + (void)execve(python, &newargv[0], &newenv[0]); + return EXECVE_FAILURE; +} + + + +/* + * Local Variables: + * c-file-style: "python" + * End: + */ diff --git a/src/common.h b/src/common.h new file mode 100644 index 00000000..c0ce0a05 --- /dev/null +++ b/src/common.h @@ -0,0 +1,61 @@ +/* common.h --- Prototypes for common routines + * + * Copyright (C) 1998,1999,2000,2001,2002 by the 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include <stdarg.h> +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif /* HAVE_SYSLOG_H */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <grp.h> +#include <unistd.h> + +/* GETGROUPS_T gets set in the makefile by configure */ +#define GID_T GETGROUPS_T + +extern void fatal(const char*, int, char*, ...); +extern void check_caller(const char*, const char*); +extern int run_script(const char*, int, char**, char**); + +/* Global variable used as a flag. */ +extern int running_as_cgi; + +/* Extern to reference this global from one of the wrapper mains */ +extern const char* logident; + +/* Exit codes, so it's easier to distinguish what caused fatal errors when + * looking at syslogs. + */ +#define GROUP_MISMATCH 2 +#define SETREGID_FAILURE 3 +#define EXECVE_FAILURE 4 +#define MAIL_USAGE_ERROR 5 +#define MAIL_ILLEGAL_COMMAND 6 +#define ADDALIAS_USAGE_ERROR 7 +#define GROUP_NAME_NOT_FOUND 8 + + +/* + * Local Variables: + * c-file-style: "python" + * End: + */ diff --git a/src/mail-wrapper.c b/src/mail-wrapper.c new file mode 100644 index 00000000..2d63a980 --- /dev/null +++ b/src/mail-wrapper.c @@ -0,0 +1,91 @@ +/* mail-wrapper.c --- Generic wrapper that will take info from a environment + * variable, and pass it to two commands. + * + * Copyright (C) 1998,1999,2000,2001,2002 by the 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "common.h" + +/* Group name that your mail programs run as. See your mail server's + * documentation for details. + */ +#define LEGAL_PARENT_GROUP MAIL_GROUP + +const char* parentgroup = LEGAL_PARENT_GROUP; +const char* logident = "Mailman mail-wrapper"; + + + +const char *VALID_COMMANDS[] = { + "admin", + "bounces", + "confirm", + "join", + "leave", + "post", + "owner", + "request", + "subscribe", + "unsubscribe", + NULL /* Sentinel, don't remove */ +}; + + +int +check_command(char *command) +{ + int i = 0; + + while (VALID_COMMANDS[i] != NULL) { + if (!strcmp(command, VALID_COMMANDS[i])) + return 1; + i++; + } + return 0; +} + + + +int +main(int argc, char** argv, char** env) +{ + int status; + + /* sanity check arguments */ + if (argc < 2) + fatal(logident, MAIL_USAGE_ERROR, + "Usage: %s program [args...]", argv[0]); + + if (!check_command(argv[1])) + fatal(logident, MAIL_ILLEGAL_COMMAND, + "Illegal command: %s", argv[1]); + + check_caller(logident, parentgroup); + + /* If we got here, everything must be OK */ + status = run_script(argv[1], argc, argv, env); + fatal(logident, status, "%s", strerror(errno)); + return status; +} + + + +/* + * Local Variables: + * c-file-style: "python" + * End: + */ diff --git a/src/vsnprintf.c b/src/vsnprintf.c new file mode 100644 index 00000000..80ce2ebb --- /dev/null +++ b/src/vsnprintf.c @@ -0,0 +1,125 @@ +/* Copyright (c) 1993 + * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) + * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) + * Copyright (c) 1987 Oliver Laumann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, 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 (see the file COPYING); if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + **************************************************************** + */ + + +/* Implementation of vsnprintf() for systems that don't have it + * (e.g. Solaris 2.5). This hasn't been tested much in the context of + * Mailman; it was ripped from screen 3.7.6's misc.c file which contains + * the above copyright. + * + * This code has been modified slightly: + * + * - use prototypes unconditionally + * - Don't use macros for stdargs calls + * - Reformat to Python C standard + * + * RMS says it's okay to include this code in Mailman but it should be kept + * in a separate file. + * + * TBD: This file needs a security audit. + */ + +#ifndef HAVE_VSNPRINTF +#include <strings.h> +#include <stdarg.h> + +int vsnprintf(char* s, size_t n, const char* fmt, va_list stack) +{ + char *f, *sf = 0; + int i, on, argl = 0; + char myf[10], buf[20]; + char *arg, *myfp; + + on = n; + f = (char*)fmt; + arg = 0; + while (arg || (sf = index(f, '%')) || (sf = f + strlen(f))) { + if (arg == 0) { + arg = f; + argl = sf - f; + } + if (argl) { + i = argl > n - 1 ? n - 1 : argl; + strncpy(s, arg, i); + s += i; + n -= i; + if (i < argl) { + *s = 0; + return on; + } + } + arg = 0; + if (sf == 0) + continue; + f = sf; + sf = 0; + if (!*f) + break; + myfp = myf; + *myfp++ = *f++; + while (((*f >= '0' && *f <='9') || *f == '#') + && myfp - myf < 8) + { + *myfp++ = *f++; + } + *myfp++ = *f; + *myfp = 0; + if (!*f++) + break; + switch(f[-1]) + { + case '%': + arg = "%"; + break; + case 'c': + case 'o': + case 'd': + case 'x': + i = va_arg(stack, int); + sprintf(buf, myf, i); + arg = buf; + break; + case 's': + arg = va_arg(stack, char *); + if (arg == 0) + arg = "NULL"; + break; + default: + arg = ""; + break; + } + argl = strlen(arg); + } + *s = 0; + return on - n; + + va_end(stack); +} +#endif /* !HAVE_VSNPRINTF */ + + +/* + * Local Variables: + * c-file-style: "python" + * End: + */ |