]> oss.titaniummirror.com Git - repo_shell.git/blob - repo_shell.c
gitcreate: add option to set description
[repo_shell.git] / repo_shell.c
1 #include <stdbool.h>
2 #include <stdio.h>
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <stdarg.h>
6 #include <sys/types.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9 #include <pwd.h>
10 #include <string.h>
11 #include "ini.h"
12 #include "utility.h"
13 #include "version.h"
14 #include "git_acl.h"
15 #include "stringutils.h"
16
17 #define CFG_FILE "/etc/repo_shell.conf"
18 #define SHELL "/bin/bash"
19 #define GIT_ACL_FILE ".gitacls"
20
21 enum { REPO_UMASK = 027 };
22
23 typedef struct {
24 char *user;
25 char *svn_root;
26 char *git_root;
27 char *owner;
28 char *git_acl_file;
29 bool allow_interactive;
30 } cfg_t;
31
32 static cfg_t cfg;
33
34 /* This is the function for which setuid root is needed for repo_shell */
35 static void change_user(char *user)
36 {
37 struct passwd *pw = getpwnam(user);
38
39 if (!pw)
40 die("invalid user %s", user);
41 setgid(pw->pw_gid);
42 setuid(pw->pw_uid);
43 }
44
45 /* Set the user and group permissions back to the requesting user */
46 static void reset_user()
47 {
48 setgid(getgid());
49 setuid(getuid());
50 }
51
52 static char *dequote(char *arg)
53 {
54 char* narg = NULL;
55
56 if (arg && *arg == '\'') {
57 char* end = arg + strlen(arg) - 1;
58
59 if (end != arg && *end == '\'') {
60 narg = arg + 1;
61 *end = '\0';
62 }
63 }
64 return narg;
65 }
66
67 static char *add_prefix(const char *prefix, const char* arg)
68 {
69 char *narg;
70 int i;
71
72 if (arg && prefix && (i = strlen(prefix))) {
73 narg = xmalloc(sizeof(char *) * (i + strlen(arg) + 2));
74 strcpy(narg, prefix);
75 strcpy(narg + i++, "/");
76 strcpy(narg + i, arg);
77 }
78 return narg;
79 }
80
81 /* Return true if the user's permissions >= those required */
82 static bool git_check_access(const char *cmd, const char *repo,
83 const char *user)
84 {
85 /* What access is required per the incoming command? Uploading commands
86 * require PERMS_READ_WRITE, while all other commands are assumed to need only
87 * PERMS_READ.
88 */
89 perms_t need = !strncmp(cmd, "git-upload", 10) ? PERMS_READ :
90 PERMS_READ_WRITE;
91 perms_t have = git_acl(user, repo, cfg.git_acl_file);
92 return have >= need;
93 }
94
95 static int do_git_cmd(const char *cmd, char *arg, char *user)
96 {
97 const char *nargv[4];
98 char* narg;
99 int ret;
100
101 if (!(arg = dequote(arg)))
102 die("bad argument");
103 if (strncmp(cmd, "git-", 4))
104 die("bad command");
105
106 change_user(cfg.owner);
107 umask(REPO_UMASK);
108 if (!git_check_access(cmd, arg, user))
109 die("insufficient ACL permissions");
110
111 nargv[0] = cmd;
112 nargv[1] = add_prefix(cfg.git_root, arg);
113 nargv[2] = NULL;
114
115 ret = execvp(nargv[0], (char *const *) nargv);
116 /* Code unreached if execv successful */
117 free((char*)nargv[1]);
118 free(narg);
119 return ret;
120 }
121
122 static int do_svnserve_cmd(const char *cmd, char *arg, char *user)
123 {
124 const char *svnserve_argv[7] = {
125 cmd, "-t", "--root", cfg.svn_root, "--tunnel-user", user, NULL
126 };
127 int ret;
128
129 change_user(cfg.owner);
130 umask(REPO_UMASK);
131 return execvp(svnserve_argv[0], (char *const *) svnserve_argv);
132 }
133
134 static void cd_to_homedir(void)
135 {
136 const char *home = getenv("HOME");
137 if (!home)
138 die("user variable HOME is unset");
139 if (chdir(home) == -1)
140 die("could not chdir to user's home directory");
141 }
142
143 static struct commands {
144 const char *name;
145 int (*exec)(const char *cmd, char *arg, char *user);
146 } cmd_list[] = {
147 { "git-receive-pack", do_git_cmd },
148 { "git-upload-pack", do_git_cmd },
149 { "git-upload-archive", do_git_cmd },
150 { "svnserve", do_svnserve_cmd },
151 { NULL },
152 };
153
154 static int ini_handler(void* user, const char* section, const char* name,
155 const char* value)
156 {
157 cfg_t* pconfig = (cfg_t*)user;
158
159 if (!strcmp(name, "svn_root"))
160 pconfig->svn_root = xstrdup(value);
161 else if (!strcmp(name, "git_root")) {
162 pconfig->git_root = xstrdup(value);
163 pconfig->git_acl_file = add_prefix(value, GIT_ACL_FILE);
164 } else if (!strcmp(name, "owner"))
165 pconfig->owner = xstrdup(value);
166 else if (!strcmp(name, "allow_interactive"))
167 pconfig->allow_interactive = str_has_word(value, pconfig->user);
168 else
169 return 0; /* unknown section/name, error */
170 return 1;
171 }
172
173 int main(int argc, char **argv)
174 {
175 char *prog;
176 const char **user_argv;
177 struct commands *cmd;
178 int devnull_fd;
179 int count;
180 struct passwd *pw;
181
182 /*
183 * Always open file descriptors 0/1/2 to avoid clobbering files
184 * in die(). It also avoids not messing up when the pipes are
185 * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
186 */
187 devnull_fd = open("/dev/null", O_RDWR);
188 while (devnull_fd >= 0 && devnull_fd <= 2)
189 devnull_fd = dup(devnull_fd);
190 if (devnull_fd == -1)
191 die("opening /dev/null failed");
192 close (devnull_fd);
193
194 if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
195 fprintf(stderr, "%s is a replacement login shell.\n"
196 " May be ran from the command line with one of these options:\n"
197 " -h|--help this text\n"
198 " -v|--version program version string\n"
199 " -t|--test <user> <repo> test access\n"
200 " -d|--detail <user> <repo> test access, outputting more detail\n"
201 , argv[0]);
202 return 0;
203 }
204
205 if (argc == 2 && (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version"))) {
206 fprintf(stderr, "%s\n", version);
207 return 0;
208 }
209
210 pw = getpwuid(getuid());
211 cfg.user = xstrdup(pw->pw_name);
212 if (ini_parse(CFG_FILE, ini_handler, &cfg) < 0)
213 die("cannot read config file %s", CFG_FILE);
214
215 if (argc == 1) {
216 if (!cfg.allow_interactive) {
217 fprintf(stderr, "\n");
218 die("only repository access is allowed");
219 }
220 reset_user();
221 argv[0] = SHELL;
222 execvp(argv[0], (char *const *) argv);
223 return 1;
224 }
225
226 if ((!strcmp(argv[1], "-d") || !strcmp(argv[1], "--detail"))) {
227 perms_t p;
228
229 if (argc !=4)
230 die("usage: %s -d|--detail <user> <repo>", argv[0]);
231 p = git_acl(argv[2], argv[3], cfg.git_acl_file);
232 fprintf(stderr,
233 "user '%s' repo '%s' perms '%s'\n via userid '%s' repoid '%s'\n",
234 argv[2], argv[3], git_acl_perms_as_str(p), git_acl_last_userid(),
235 git_acl_last_repoid());
236 return 0;
237 }
238
239 if ((!strcmp(argv[1], "-t") || !strcmp(argv[1], "--test"))) {
240 perms_t p;
241
242 if (argc !=4)
243 die("usage: %s -t|--test <user> <repo>", argv[0]);
244 p = git_acl(argv[2], argv[3], cfg.git_acl_file);
245 printf("%s\n", git_acl_perms_as_str(p));
246 return 0;
247 }
248
249 if (argc == 3) {
250 /* argv[0] = repo_shell, argv[1] = -c, argv[2] = cmd
251 * cmd = "svnserve -t" or "git-xxx '/path/to/repo.git'"
252 */
253 prog = xstrdup(argv[2]);
254 if (!strncmp(prog, "git", 3) && isspace(prog[3]))
255 /* Accept "git foo" as if the caller said "git-foo". */
256 prog[3] = '-';
257
258 for (cmd = cmd_list ; cmd->name ; cmd++) {
259 int len = strlen(cmd->name);
260 char *arg;
261 if (strncmp(cmd->name, prog, len))
262 continue;
263 arg = NULL;
264 switch (prog[len]) {
265 case '\0':
266 arg = NULL;
267 break;
268 case ' ':
269 arg = prog + len + 1;
270 break;
271 default:
272 continue;
273 }
274
275 exit(cmd->exec(cmd->name, arg, cfg.user));
276 }
277 }
278
279 if (!cfg.allow_interactive)
280 die("only repository access is allowed");
281 reset_user();
282 cd_to_homedir();
283 argv[0] = SHELL;
284 execvp(argv[0], (char *const *) argv);
285 }