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