summaryrefslogtreecommitdiff
path: root/IkiWiki/Wrapper.pm
blob: 4fe2d8111d0a76714f37387f41da4d2666f3f5e2 (plain)
  1. #!/usr/bin/perl
  2. package IkiWiki;
  3. use warnings;
  4. use strict;
  5. use File::Spec;
  6. use Data::Dumper;
  7. use IkiWiki;
  8. sub gen_wrappers () {
  9. debug(gettext("generating wrappers.."));
  10. my %origconfig=(%config);
  11. foreach my $wrapper (@{$config{wrappers}}) {
  12. %config=(%origconfig, %{$wrapper});
  13. $config{verbose}=$config{setupverbose}
  14. if exists $config{setupverbose};
  15. $config{syslog}=$config{setupsyslog}
  16. if exists $config{setupsyslog};
  17. delete @config{qw(setupsyslog setupverbose wrappers genwrappers rebuild)};
  18. checkconfig();
  19. if (! $config{cgi} && ! $config{post_commit} &&
  20. ! $config{test_receive}) {
  21. $config{post_commit}=1;
  22. }
  23. gen_wrapper();
  24. }
  25. %config=(%origconfig);
  26. }
  27. sub gen_wrapper () {
  28. $config{srcdir}=File::Spec->rel2abs($config{srcdir});
  29. $config{destdir}=File::Spec->rel2abs($config{destdir});
  30. my $this=File::Spec->rel2abs($0);
  31. if (! -x $this) {
  32. error(sprintf(gettext("%s doesn't seem to be executable"), $this));
  33. }
  34. if ($config{setup}) {
  35. error(gettext("cannot create a wrapper that uses a setup file"));
  36. }
  37. my $wrapper=possibly_foolish_untaint($config{wrapper});
  38. if (! defined $wrapper || ! length $wrapper) {
  39. error(gettext("wrapper filename not specified"));
  40. }
  41. delete $config{wrapper};
  42. my @envsave;
  43. push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
  44. CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE
  45. HTTP_COOKIE REMOTE_USER HTTPS REDIRECT_STATUS
  46. HTTP_HOST SERVER_PORT HTTPS
  47. REDIRECT_URL} if $config{cgi};
  48. my $envsave="";
  49. foreach my $var (@envsave) {
  50. $envsave.=<<"EOF";
  51. if ((s=getenv("$var")))
  52. addenv("$var", s);
  53. EOF
  54. }
  55. my @wrapper_hooks;
  56. run_hooks(genwrapper => sub { push @wrapper_hooks, shift->() });
  57. my $check_commit_hook="";
  58. my $pre_exec="";
  59. if ($config{post_commit}) {
  60. # Optimise checking !commit_hook_enabled() ,
  61. # so that ikiwiki does not have to be started if the
  62. # hook is disabled.
  63. #
  64. # Note that perl's flock may be implemented using fcntl
  65. # or lockf on some systems. If so, and if there is no
  66. # interop between the locking systems, the true C flock will
  67. # always succeed, and this optimisation won't work.
  68. # The perl code will later correctly check the lock,
  69. # so the right thing will still happen, though without
  70. # the benefit of this optimisation.
  71. $check_commit_hook=<<"EOF";
  72. {
  73. int fd=open("$config{wikistatedir}/commitlock", O_CREAT | O_RDWR, 0666);
  74. if (fd != -1) {
  75. if (flock(fd, LOCK_SH | LOCK_NB) != 0)
  76. exit(0);
  77. close(fd);
  78. }
  79. }
  80. EOF
  81. }
  82. elsif ($config{cgi}) {
  83. # Avoid more than one ikiwiki cgi running at a time by
  84. # taking a cgi lock. Since ikiwiki uses several MB of
  85. # memory, a pile up of processes could cause thrashing
  86. # otherwise. The fd of the lock is stored in
  87. # IKIWIKI_CGILOCK_FD so unlockwiki can close it.
  88. $pre_exec=<<"EOF";
  89. lockfd=open("$config{wikistatedir}/cgilock", O_CREAT | O_RDWR, 0666);
  90. if (lockfd != -1 && flock(lockfd, LOCK_EX) == 0) {
  91. char *fd_s=malloc(8);
  92. sprintf(fd_s, "%i", lockfd);
  93. setenv("IKIWIKI_CGILOCK_FD", fd_s, 1);
  94. }
  95. EOF
  96. }
  97. my $set_background_command='';
  98. if (defined $config{wrapper_background_command} &&
  99. length $config{wrapper_background_command}) {
  100. my $background_command=delete $config{wrapper_background_command};
  101. $set_background_command=~s/"/\\"/g;
  102. $set_background_command='#define BACKGROUND_COMMAND "'.$background_command.'"';
  103. }
  104. $Data::Dumper::Indent=0; # no newlines
  105. my $configstring=Data::Dumper->Dump([\%config], ['*config']);
  106. $configstring=~s/\\/\\\\/g;
  107. $configstring=~s/"/\\"/g;
  108. $configstring=~s/\n/\\n/g;
  109. writefile(basename("$wrapper.c"), dirname($wrapper), <<"EOF");
  110. /* A wrapper for ikiwiki, can be safely made suid. */
  111. #include <stdio.h>
  112. #include <sys/types.h>
  113. #include <sys/stat.h>
  114. #include <fcntl.h>
  115. #include <unistd.h>
  116. #include <stdlib.h>
  117. #include <string.h>
  118. #include <sys/file.h>
  119. extern char **environ;
  120. char *newenviron[$#envsave+7];
  121. int i=0;
  122. void addenv(char *var, char *val) {
  123. char *s=malloc(strlen(var)+1+strlen(val)+1);
  124. if (!s)
  125. perror("malloc");
  126. sprintf(s, "%s=%s", var, val);
  127. newenviron[i++]=s;
  128. }
  129. int main (int argc, char **argv) {
  130. int lockfd=-1;
  131. char *s;
  132. $check_commit_hook
  133. @wrapper_hooks
  134. $envsave
  135. newenviron[i++]="HOME=$ENV{HOME}";
  136. newenviron[i++]="PATH=$ENV{PATH}";
  137. newenviron[i++]="WRAPPED_OPTIONS=$configstring";
  138. #ifdef __TINYC__
  139. /* old tcc versions do not support modifying environ directly */
  140. if (clearenv() != 0) {
  141. perror("clearenv");
  142. exit(1);
  143. }
  144. for (; i>0; i--)
  145. putenv(newenviron[i-1]);
  146. #else
  147. newenviron[i]=NULL;
  148. environ=newenviron;
  149. #endif
  150. if (setregid(getegid(), -1) != 0 &&
  151. setregid(getegid(), -1) != 0) {
  152. perror("failed to drop real gid");
  153. exit(1);
  154. }
  155. if (setreuid(geteuid(), -1) != 0 &&
  156. setreuid(geteuid(), -1) != 0) {
  157. perror("failed to drop real uid");
  158. exit(1);
  159. }
  160. $pre_exec
  161. $set_background_command
  162. #ifdef BACKGROUND_COMMAND
  163. if (lockfd != -1) {
  164. close(lockfd);
  165. }
  166. pid_t pid=fork();
  167. if (pid == -1) {
  168. perror("fork");
  169. exit(1);
  170. }
  171. else if (pid == 0) {
  172. execl("$this", "$this", NULL);
  173. perror("exec $this");
  174. exit(1);
  175. }
  176. else {
  177. waitpid(pid, NULL, 0);
  178. if (daemon(1, 0) == 0) {
  179. system(BACKGROUND_COMMAND);
  180. exit(0);
  181. }
  182. else {
  183. perror("daemon");
  184. exit(1);
  185. }
  186. }
  187. #else
  188. execl("$this", "$this", NULL);
  189. perror("exec $this");
  190. exit(1);
  191. #endif
  192. }
  193. EOF
  194. my @cc=exists $ENV{CC} ? possibly_foolish_untaint($ENV{CC}) : 'cc';
  195. push @cc, possibly_foolish_untaint($ENV{CFLAGS}) if exists $ENV{CFLAGS};
  196. if (system(@cc, "$wrapper.c", "-o", "$wrapper.new") != 0) {
  197. #translators: The parameter is a C filename.
  198. error(sprintf(gettext("failed to compile %s"), "$wrapper.c"));
  199. }
  200. unlink("$wrapper.c");
  201. if (defined $config{wrappergroup}) {
  202. my $gid=(getgrnam($config{wrappergroup}))[2];
  203. if (! defined $gid) {
  204. error(sprintf("bad wrappergroup"));
  205. }
  206. if (! chown(-1, $gid, "$wrapper.new")) {
  207. error("chown $wrapper.new: $!");
  208. }
  209. }
  210. if (defined $config{wrappermode} &&
  211. ! chmod(oct($config{wrappermode}), "$wrapper.new")) {
  212. error("chmod $wrapper.new: $!");
  213. }
  214. if (! rename("$wrapper.new", $wrapper)) {
  215. error("rename $wrapper.new $wrapper: $!");
  216. }
  217. #translators: The parameter is a filename.
  218. debug(sprintf(gettext("successfully generated %s"), $wrapper));
  219. }
  220. 1