summaryrefslogtreecommitdiff
path: root/pinyweb/suid/piny-suid.c
blob: 251e3e10f622f65045e99ebcb961e93d8433487d (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
#include <errno.h>
#include <pwd.h>
#include <regex.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>

int main( int argc, char *argv[] ) {

  int err;

  openlog( "piny-suid", LOG_ODELAY, LOG_AUTHPRIV );

  if ( argc < 3 ) {
    syslog( LOG_ERR, "Invalid usage" );
    fprintf( stderr, "Usage: %s username command [args ...]\n", argv[0] );
    return 1;
  };

  regex_t user_reg;

  // Please note that these regular expressions should duplicate the language for usernames described in Piny::User.
  if ( ( err = regcomp( &user_reg, "^[a-zA-Z][a-zA-Z0-9_.-]*$", REG_EXTENDED | REG_NOSUB ) ) != 0 ) {
    size_t sz = regerror( err, &user_reg, NULL, 0 );
    char buf[sz];
    regerror( err, &user_reg, buf, sz );
    syslog( LOG_CRIT, "Internal error; first regex, %s", buf );
    fprintf( stderr, "Internal error compiling first regular expression: %s\n", buf );
    return 1;
  };

  if ( regexec( &user_reg, argv[1], 0, NULL, 0 ) ) {
    syslog( LOG_ERR, "Invalid username '%s' (first regex)", argv[1] );
    fprintf( stderr, "'%s' does not appear to be a valid username!\n", argv[1] );
    return 1;
  };

  regfree( &user_reg );

  if ( ( err = regcomp( &user_reg, "^(git|ikiwiki)-", REG_EXTENDED | REG_NOSUB ) ) != 0 ) {
    size_t sz = regerror( err, &user_reg, NULL, 0 );
    char buf[sz];
    regerror( err, &user_reg, buf, sz );
    syslog( LOG_CRIT, "Internal error: second regex, %s", buf );
    fprintf( stderr, "Internal error compiling second regular expression: %s\n", buf );
    return 1;
  };

  if ( ! regexec( &user_reg, argv[1], 0, NULL, 0 ) ) {
    syslog( LOG_ERR, "Invalid username '%s' (second regex)", argv[1] );
    fprintf( stderr, "'%s' does not appear to be a valid username!\n", argv[1] );
    return 1;
  };

  regfree( &user_reg );

  regex_t cmd_reg;

  if ( ( err = regcomp( &cmd_reg, "/", REG_EXTENDED | REG_NOSUB ) ) != 0 ) {
    size_t sz = regerror( err, &cmd_reg, NULL, 0 );
    char buf[sz];
    regerror( err, &cmd_reg, buf, sz );
    syslog( LOG_CRIT, "Internal error: third regex, %s", buf );
    fprintf( stderr, "Internal error compiling third regular expression: %s\n", buf );
    return 1;
  };

  if ( ! regexec( &cmd_reg, argv[2], 0, NULL, 0 ) ) {
    syslog( LOG_ERR, "Invalid command '%s' (third regex)", argv[2] );
    fprintf( stderr, "'%s' does not appear to be a valid command!\n", argv[2] );
    return 1;
  };

  regfree( &cmd_reg );

  struct passwd *pwd = getpwnam( argv[1] );

  if ( ! pwd ) {
    syslog( LOG_ERR, "Invalid username '%s' (getpwnam)", argv[1] );
    fprintf( stderr, "'%s' does not appear to be a valid username!\n", argv[1] );
    return 1;
  };

  if ( pwd->pw_uid < 1000 ) {
    syslog( LOG_ERR, "Invalid username '%s' (uid)", argv[1] );
    fprintf( stderr, "'%s' does not appear to be a valid username!\n", argv[1] );
    return 1;
  };

  if ( setregid( pwd->pw_gid, pwd->pw_gid ) != 0 ) {
    err = errno;
    syslog( LOG_ERR, "Unable to change GID: %s, %s", argv[2], strerror( err ) );
    fprintf( stderr, "Unable to change GID: %s\n", strerror( err ) );
    return 1;
  };

  if ( setreuid( pwd->pw_uid, pwd->pw_uid ) != 0 ) {
    err = errno;
    syslog( LOG_ERR, "Unable to change UID: %s, %s", argv[2], strerror( err ) );
    fprintf( stderr, "Unable to change UID: %s\n", strerror( err ) );
    return 1;
  };

  size_t sz = snprintf( NULL, 0, "/usr/share/piny-suid/%s", argv[2] );
  char buf[sz];
  snprintf( buf, sz, "/usr/share/piny-suid/%s", argv[2] );

  char * const env[] =
    { NULL
    };

  syslog( LOG_NOTICE, "Going to exec '%s' as '%s'", argv[2], argv[1] );
  execve( buf, argv + 2, env );

  syslog( LOG_ERR, "Invalid command '%s' (fell past exec)", argv[2] );
  fprintf( stderr, "'%s' does not appear to be a valid command!\n", argv[2] );
  return 1;

};