summaryrefslogtreecommitdiff
path: root/doc/historical/sbuild-chroot-helper.c
blob: 0399b64ac5259fbc30974c57f376bcf8ba82eca7 (plain)
1
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
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/*
 * This program file is included solely for sentimental and historical
 * reasons.  It should not be used.
 *
 * This C program is the earliest available incarnation of schroot,
 * dating from 11/05/2005, from before it was put under version
 * control (GNU Arch back then, rather than git today) and before the
 * initial 0.1.0 release.  In fact, it was then known as
 * "sbuild-chroot-helper", until it was renamed to the shorter and
 * less unwieldy "schroot" a few weeks later.  At this point, schroot
 * is an incomplete but partly-functional work-in-progress:
 *
 * • The chroot location is hard-coded
 * • The configuration file is hard-coded
 * • The configuration file is read but not actually used
 * • The command-line options are mostly non-functional
 * • Running commands is not permitted; only a login shell
 * • It lacks sessions
 * • No PAM support
 * • No setup scripts (no filesystem mounting and other setup)
 * • Only "plain" type chroots are supported
 * • Written in plain C using GLib (we live and learn from our mistakes)
 *
 * From this humble start, schroot rapidly took off.  Over the next
 * year it acquired most of the core features seen today.  Just six
 * months after writing in GLib/GObject-based C, it was converted
 * entirely to C++ using Boost, which vastly improved both the code
 * quality and its maintainability.
 *
 * This version is just 231 lines of C, while 0.1.0 is 2064 lines of
 * C.  In comparison, the current version of schroot (1.4.19 at the
 * time of writing) weighs in at 19880 lines of C++.  The current
 * schroot is not as fast as it was back then, but nowadays it does so
 * much more, and is used as an indispensable tool for the daily work
 * of thousands of people, including playing a critical role in the
 * Debian build dæmon network, where it is used to maintain and build
 * Debian packages in dedicated build chroots.
 *
 * It's been an interesting and enjoyable 5½ years creating a tool
 * initially just for my own use with sbuild, but which really caught
 * people's imaginations, and was put to all sorts of exciting and
 * novel uses.  Without the valuable input from lots of different
 * people who all wanted schroot to do something different, its
 * quality and feature set would never have become so great.  I hope
 * that schroot will continue to be used and improve over the coming
 * years!
 *
 *         Roger Leigh
 *         15/01/2011
 *
 *****************************************************************************/

/*
 * sbuild-chroot-helper.c:
 * Securely enter a chroot and exec a login shell or user command
 * Copyright © 2002,2005  Roger Leigh <rleigh@debian.org>
 *
 * 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
 *
 *****************************************************************************/

#define _GNU_SOURCE
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <grp.h>
#include <pwd.h>
#include <fcntl.h>
#include <unistd.h>

#include <glib.h>

#define SBUILD_GROUP "sbuild"
#define CHROOT "/usr/local/chroot/unstable"

typedef struct _SbuildChroot SbuildChroot;

struct SbuildChroot
{
  char *name;
  char *path;
};

typedef enum
{
  SBUILD_CONFIG_FILE_ERROR_STAT_FAIL,
  SBUILD_CONFIG_FILE_ERROR_OWNERSHIP,
  SBUILD_CONFIG_FILE_ERROR_PERMISSIONS,
  SBUILD_CONFIG_FILE_ERROR_NOT_REGULAR
} SbuildConfigFileError;

#define SBUILD_CONFIG_FILE_ERROR sbuild_config_file_error_quark()

GQuark
sbuild_config_file_error_quark (void)
{
  static GQuark error_quark = 0;

  if (error_quark == 0)
    error_quark = g_quark_from_static_string ("sbuild-config-file-error-quark");

  return error_quark;
}


static struct {
  const char *newroot;
  gboolean preserve;
  gboolean quiet;
  gboolean list;
  gboolean info;
  gboolean all;
} opt;

static GOptionEntry entries[] =
{
  { "all", 'a', 0, G_OPTION_ARG_NONE, &opt.all, "Run command in all chroots", NULL },
  { "chroot", 'c', 0, G_OPTION_ARG_STRING, &opt.newroot, "Use specified chroot", "chroot" },
  { "list", 'l', 0, G_OPTION_ARG_NONE, &opt.list, "List available chroots", NULL },
  { "info", 'i', 0, G_OPTION_ARG_NONE, &opt.info, "Show information about chroot", NULL },
  { "preserve-environment", 'p', 0, G_OPTION_ARG_NONE, &opt.preserve, "Preserve user environment", NULL },
  { "quiet", 'q', 0, G_OPTION_ARG_NONE, &opt.quiet, "Show less output", NULL },
};

static void
parse_options(int   argc,
              char *argv[])
{
  GError *error = NULL;

  GOptionContext *context = g_option_context_new ("- run command or shell in a chroot");
  g_option_context_add_main_entries (context, entries, NULL);
  g_option_context_parse (context, &argc, &argv, &error);
}

gboolean
config_check_security(int      fd,
                      GError **error)
{
  struct stat statbuf;
  if (fstat(fd, &statbuf) < 0)
    {
      g_set_error(error,
                  SBUILD_CONFIG_FILE_ERROR, SBUILD_CONFIG_FILE_ERROR_STAT_FAIL,
                  "failed to stat file: %s", strerror(errno));
      return FALSE;
    }

  if (statbuf.st_uid != 0 || statbuf.st_gid != 0)
    {
      g_set_error(error,
                  SBUILD_CONFIG_FILE_ERROR, SBUILD_CONFIG_FILE_ERROR_OWNERSHIP,
                  "not owned by user and group root", strerror(errno));
      return FALSE;
    }

  if (statbuf.st_mode & S_IWOTH)
    {
      g_set_error(error,
                  SBUILD_CONFIG_FILE_ERROR, SBUILD_CONFIG_FILE_ERROR_PERMISSIONS,
                  "others have write permission");
      return FALSE;
    }

  if (!S_ISREG(statbuf.st_mode))
    {
      g_set_error(error,
                  SBUILD_CONFIG_FILE_ERROR, SBUILD_CONFIG_FILE_ERROR_NOT_REGULAR,
                  "not a regular file");
      return FALSE;
    }

  return TRUE;
}

GList *
load_config(const char *file)
{
  int fd = open(file, O_RDONLY|O_NOFOLLOW);
  if (fd < 0)
    {
      g_printerr("%s: failed to load configuration: %s\n", file, strerror(errno));
      exit (EXIT_FAILURE);
    }

  GError *security_error = NULL;
  config_check_security(fd, &security_error);
  if (security_error)
    {
      g_printerr("%s: security failure: %s\n", file, security_error->message);
      exit (EXIT_FAILURE);
    }

  GKeyFile *keyfile = g_key_file_new();
  GError *error = NULL;
  g_key_file_load_from_file(keyfile, file, G_KEY_FILE_NONE, &error);

  if (error)
    {
      g_printerr("%s: parse failure: %s\n", file, error->message);
      exit (EXIT_FAILURE);
    }

}

gboolean
is_sbuild_member (void)
{
  errno = 0;
  struct group *sbuild_group = getgrnam(SBUILD_GROUP);
  if (sbuild_group == NULL)
    {
      if (errno == 0)
        fprintf(stderr, "group " SBUILD_GROUP " not found\n");
      else
        fprintf(stderr, "group " SBUILD_GROUP " not found: %s\n", strerror(errno));
      exit (EXIT_FAILURE);
    }

  int supp_group_count = getgroups(0, NULL);
  if (supp_group_count < 0)
    {
      fprintf(stderr, "can't get supplementary group count: %s\n", strerror(errno));
      exit (EXIT_FAILURE);
    }
  int supp_groups[supp_group_count];
  if (getgroups(supp_group_count, supp_groups) < 1)
    {
      fprintf(stderr, "can't get supplementary groups: %s\n", strerror(errno));
      exit (EXIT_FAILURE);
    }

  bool sbuild_group_member = false;

  for (int i = 0; i < supp_group_count; ++i)
    {
      if (sbuild_group->gr_gid == supp_groups[i])
        sbuild_group_member = true;
    }

  return sbuild_group_member;
}

int
main (int   argc,
      char *argv[])
{
  parse_options(argc, argv);
  load_config("test.conf");

  if (is_sbuild_member() == false)
    {
      fprintf (stderr, "Permission denied: not an sbuild group member\n");
      exit (EXIT_FAILURE);
    }

  uid_t uid;
  gid_t gid;
  struct passwd *pass;
  const char *shell;

  /* Get user and group IDs */
  uid = getuid ();
  if ((pass = getpwuid (uid)) == NULL)
    {
      fprintf (stderr, "Could not get username for user %lu\n", (unsigned long) uid);
      exit (EXIT_FAILURE);
    }
  gid = pass->pw_gid;

  /* Set group ID and supplementary groups */
  if (setgid (gid))
    {
      fprintf (stderr, "Could not set gid to %lu\n", (unsigned long) gid);
      exit (EXIT_FAILURE);
    }
  if (initgroups (pass->pw_name, gid))
    {
      fprintf (stderr, "Could not set supplementary group IDs\n");
      exit (EXIT_FAILURE);
    }

  /* Enter the chroot */
  if (chdir (CHROOT))
    {
      fprintf (stderr, "Could not chdir to %s: %s\n", CHROOT,
               strerror (errno));
      exit (EXIT_FAILURE);
    }
  if (chroot (CHROOT))
    {
      fprintf (stderr, "Could not chroot to %s: %s\n", CHROOT,
               strerror (errno));
      exit (EXIT_FAILURE);
    }
  /* printf ("Entered chroot: %s\n", CHROOT); */

  /* Set uid and check we are not still root */
  if (setuid (uid))
    {
      fprintf (stderr, "Could not set uid to %lu\n", (unsigned long) uid);
      exit (EXIT_FAILURE);
    }
  if (!setuid (0) && uid)
    {
      fprintf (stderr, "Failed to drop root permissions!\n");
      exit (EXIT_FAILURE);
    }

  /* Set up environment */
  if (pass->pw_dir)
    setenv("HOME", pass->pw_dir, 1);
  else
    setenv("HOME", "/", 1);

  /* chdir to home directory */
  if (chdir (getenv("HOME")))
    {
      fprintf (stderr, "warning: Could not chdir to %s: %s\n", getenv("HOME"),
               strerror (errno));
    }

  /* Run login shell */
  if (pass->pw_shell)
    shell = pass->pw_shell;
  else
    shell = "/bin/false";
  if (execl (shell, shell, (const char *) NULL))
    {
      fprintf (stderr, "Could not exec %s: %s\n", shell, strerror (errno));
      exit (EXIT_FAILURE);
    }
  /* This should never be reached */
  exit(EXIT_FAILURE);
}