1#!/usr/bin/env perl 2 3# --------------------------------------------------------------------------- 4# Copyright (C) 2008-2020 TJ Saunders <tj@castaglia.org> 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. 19# --------------------------------------------------------------------------- 20 21use strict; 22 23use File::Basename qw(basename); 24use Getopt::Long; 25 26Getopt::Long::Configure("no_ignorecase"); 27 28my $prog = basename($0); 29 30my $compiler = q(@CC@); 31my $cflags = q(@OSTYPE@ @OSREL@ @CFLAGS@ -DPR_SHARED_MODULE); 32my $cppflags = q(@CPPFLAGS@); 33my $ltdl_ldflags = q(@MODULE_LDFLAGS@); 34my $sbindir = q(@SBINDIR@); 35my $includedir = q(@INCLUDEDIR@); 36my $installer = q(@INSTALL@); 37my $install_strip = q(@INSTALL_STRIP@); 38my $libexecdir = q(@LIBEXECDIR@); 39my $libtool = 'libtool'; 40 41if (!defined($ENV{LIBTOOL})) { 42 if ($^O eq 'darwin') { 43 $libtool = 'glibtool'; 44 } 45 46} else { 47 $libtool = $ENV{LIBTOOL}; 48} 49 50my $opts = {}; 51GetOptions($opts, 'c|compile', 'i|install', 'd|clean', 'h|help', 'name=s', 52 'D=s@', 'I=s@', 'L=s@', 'l=s@', 'W=s@'); 53 54if ($opts->{h}) { 55 usage(); 56 exit 0; 57} 58 59# Make sure we can query proftpd to find out its list of installed modules. 60# Unless we see mod_dso listed, there's no point in compiling a shared 61# module for proftpd to use. 62 63my $proftpd = "$sbindir/proftpd"; 64unless (-x $proftpd) { 65 print STDERR "$proftpd not found or not executable\n"; 66 exit 1; 67} 68 69unless (grep /mod_dso/, `$proftpd -l`) { 70 print STDERR "\nYour installed proftpd does not support shared modules/DSOs.\n"; 71 print STDERR "Make sure the --enable-dso configure option is used when\n"; 72 print STDERR "compiling proftpd.\n\n"; 73 exit 1; 74} 75 76# If the libtool-specific linker flags are empty, it means that the 77# script was generated by a configure script lacking the --enable-dso option. 78unless (length($ltdl_ldflags) > 0) { 79 print STDERR "\nMissing the required libtool flags.\n"; 80 print STDERR "Make sure the --enable-dso configure option is used when\n"; 81 print STDERR "compiling proftpd.\n\n"; 82 exit 1; 83} 84 85# Now, depending on the requested mode (compile/install/clean), build up 86# and execute the commands. 87 88my $mod_name = get_module_name(); 89 90if (defined($opts->{c})) { 91 my $srcs = []; 92 my $objs = []; 93 94 my $configure_script; 95 96 foreach my $file (@ARGV) { 97 if ($file =~ /\.c$/) { 98 push(@$srcs, $file); 99 100 if ($file =~ /\/?mod_[^\/]+\.c$/) { 101 $configure_script = $file; 102 $configure_script =~ s/(\/?)mod_[^\/]+\.c$/\1configure/; 103 104 if ($configure_script !~ /^\//) { 105 $configure_script = './' . $configure_script; 106 } 107 } 108 109 my $obj = $file; 110 $obj =~ s/\.c$/\.lo/; 111 112 # Handle an argument such as 'contrib/mod_sql_sqlite.c' by keeping 113 # just the name portion of the path. 114 $obj =~ s/^(.*)\///g; 115 116 push(@$objs, $obj); 117 118 } else { 119 print STDERR "Cannot compile non-.c file $file, aborting\n"; 120 exit 1; 121 } 122 } 123 124 if (defined($configure_script)) { 125 # Check for any configure script for this module. If present, error out 126 # for now. 127 if (-f $configure_script) { 128 print STDERR "\nCannot compile $mod_name using prxs. Use existing $configure_script script instead:\n\n"; 129 130 if ($configure_script ne './configure') { 131 my $mod_dir = $configure_script; 132 $mod_dir =~ s/configure$//g; 133 print STDERR " cd $mod_dir\n"; 134 } 135 136 print STDERR " ./configure\n"; 137 print STDERR " make\n"; 138 print STDERR " make install\n"; 139 exit 1; 140 } 141 } 142 143 foreach my $def (@{ $opts->{D} }) { 144 if ($def =~ /^(\S+)=(\S+)$/) { 145 $cflags .= " -D'$1=$2'"; 146 147 } else { 148 $cflags .= " -D$def"; 149 } 150 } 151 152 $cflags .= " -I. -I$includedir/proftpd"; 153 154 foreach my $incdir (@{ $opts->{I} }) { 155 $cflags .= " -I$incdir"; 156 } 157 158 my $cmds = []; 159 foreach my $src (@$srcs) { 160 push(@$cmds, "$libtool --mode=compile $compiler $cflags -c $src"); 161 } 162 163 run_cmds($cmds); 164 165 my $objlist = ''; 166 foreach my $obj (@$objs) { 167 $objlist .= " $obj"; 168 } 169 170 my $ldflags .= " $ltdl_ldflags"; 171 172 foreach my $libdir (@{ $opts->{L} }) { 173 $ldflags .= " -L$libdir"; 174 } 175 176 # Scan through the .c files, looking for the $Libraries$ hint that 177 # proftpd's build system uses. 178 foreach my $src (@$srcs) { 179 if (open(my $fh, "< $src")) { 180 while (my $line = <$fh>) { 181 chomp($line); 182 183 if ($line =~ /\$Libraries:\s+(.*)?\$/) { 184 my $hint = $1; 185 186 # Assume that the library hint list is space-separated; add them 187 # to the $opts hashref. Don't forget to strip of the '-l' prefix; 188 # that is added back later in the handling of $opts. 189 my $libs = [split(/\s+/, $hint)]; 190 foreach my $lib (@$libs) { 191 $lib =~ s/^\-l//; 192 push(@{ $opts->{l} }, $lib); 193 } 194 195 last; 196 } 197 } 198 199 close($fh); 200 201 } else { 202 print STDERR "Unable to scan $src for \$Libraries\$ hint: $!\n"; 203 } 204 } 205 206 my $libs = ""; 207 foreach my $lib (@{ $opts->{l} }) { 208 $libs .= " -l$lib"; 209 } 210 211 $cmds = []; 212 push(@$cmds, "$libtool --mode=link $compiler -o $mod_name.la -rpath $libexecdir $ldflags $objlist $libs"); 213 214 run_cmds($cmds); 215} 216 217if (defined($opts->{i})) { 218 my $cmds = []; 219 push(@$cmds, "$libtool --mode=install $installer $install_strip $mod_name.la $ENV{DESTDIR}$libexecdir"); 220 221 run_cmds($cmds); 222 223 # Don't forget to remind the user to manually edit their proftpd.conf 224 # and add the LoadModule to load the just-installed module. 225 226 my $load_mod_name = $mod_name; 227 $load_mod_name =~ s/^(.*)\///; 228 229 print STDOUT "\nTo load your newly installed module into proftpd, be sure\n"; 230 print STDOUT "to edit your proftpd.conf and add the following:\n\n"; 231 print STDOUT " <IfModule mod_dso.c>\n"; 232 print STDOUT " LoadModule $load_mod_name.c\n"; 233 print STDOUT " </IfModule>\n\n"; 234 print STDOUT "and then restart your proftpd server, so that the config change\n"; 235 print STDOUT "becomes live.\n\n"; 236} 237 238if (defined($opts->{d})) { 239 my $cmds = []; 240 push(@$cmds, "$libtool --mode=clean rm -f $mod_name.la *.lo"); 241 242 run_cmds($cmds); 243} 244 245if (!defined($opts->{c}) && 246 !defined($opts->{i}) && 247 !defined($opts->{d})) { 248 print STDERR "No compile, install, or clean mode requested, exiting\n"; 249 exit 1; 250} 251 252exit 0; 253 254sub get_module_name { 255 # Determine the name of the module (e.g. "mod_foo") being operated upon. 256 if (defined($opts->{n})) { 257 return $opts->{n}; 258 } 259 260 foreach my $file (@ARGV) { 261 # Handle an argument such as 'contrib/mod_sql_sqlite.c' by keeping 262 # just the name portion of the path. 263 264 if ($file =~ /\/?mod_([^\/]+)\.(c|la)$/) { 265 return "mod_$1"; 266 } 267 } 268 269 return "mod_unknown"; 270} 271 272sub run_cmds { 273 my $cmds = shift; 274 275 foreach my $cmd (@$cmds) { 276 print STDOUT "$cmd\n"; 277 278 my $res = system($cmd); 279 if ($res) { 280 print STDERR "$prog: error executing command (", $res >> 8, ")\n"; 281 exit 1; 282 } 283 } 284} 285 286sub usage { 287 my $prog = basename($0); 288 289 print STDOUT <<EOU; 290 291usage: $prog <action> <opts> <source files> 292 293Actions: 294 295 -c, --compile Compiles the listed .c source files into a proftpd 296 DSO module. 297 298 -i, --install Installs a compiled proftpd DSO module into the 299 directory where proftpd expects to find loadable 300 DSO modules. 301 302 -d, --clean Removes any generated files, returning the build 303 directory to a clean state. 304 305Options: 306 307 -h, --help Displays this message. 308 309 -n, --name Tells prxs the name of the module being compiled. 310 By default, prxs determines the module name from 311 the list of .c files listed, expecting to see a 312 "mod_\$name.c" file. 313 314 -D key Passes these macros through to the compilation step. 315 -D key=value Note that the space before the key is important. 316 317 -I includedir Specify additional include file search directories. 318 Note that the space before the directory is important. 319 320 -L libdir Specify additional library file search directories. 321 Note that the space before the directory is important. 322 323 -l library Specify additional libraries for linking. 324 Note that the space before the library name is important. 325 326At least one of the above actions must be specified when using prxs. More 327than one action can be specified at the same time. 328 329To use prxs all in one step, you could do: 330 331 prxs -c -i -d mod_custom.c 332 333EOU 334} 335