/* * @(#) $Header: /tmp/cvs/mysql-admutils/mysql-dbadm.c,v 1.21 2007-06-07 11:43:52 geirha Exp $ * * mysql-dbadm.c * */ #include #include #include #include #include #include #include #include #include #include #include #include "mysql-admutils.h" /* New database names may only use these characters in their identifier */ const char dbname_validchars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; /* Returns true if dbname contains only characters in dbname_validchars. */ int dbname_isclean(char* dbname) { int reallen, cleanlen; reallen = strlen(dbname); cleanlen = strspn(dbname, dbname_validchars); return (reallen == cleanlen); } char * strchr_whitespace(const char *s) { char *sp, *tab; sp = strchr(s, ' '); tab = strchr(s, '\t'); if (sp == NULL) return tab; if (tab == NULL) return sp; return sp < tab ? sp : tab; /* if both are found, return the first one */ } char * strtok_whitespace(char *s) { static char *cp; char *r; r = s ? s : cp; if (r == NULL) return NULL; cp = strchr_whitespace(r); if (cp == NULL) return r; while ((*cp == ' ') || (*cp == '\t')) *cp++ = '\0'; return r; } int valid_priv(const char *s) { if (s == NULL) return 0; #define ACCEPT(x) if (strcmp(s, x) == 0) return 1 ACCEPT("Y"); ACCEPT("N"); ACCEPT("y"); ACCEPT("n"); #undef ACCEPT return 0; /* not a valid priv */ } int usage() { printf("Usage: %s COMMAND [DATABASE]...\n", program_name); printf("Create, drop og edit permission for the DATABASE(s),\n"); printf("as determined by the COMMAND. Valid COMMANDs:\n"); printf("\n"); printf(" create create the DATABASE(s).\n"); printf(" drop delete the DATABASE(s).\n"); printf(" show give information about the DATABASE(s), or, if\n"); printf(" none are given, all the ones you own.\n"); printf(" editperm change permissions for the DATABASE(s). Your\n"); printf(" favorite editor will be started, allowing you\n"); printf(" to make changes to the permission table.\n"); printf(" Run '%s --help-editperm' for more\n" " information.\n",program_name); printf("\n"); printf("Report bugs to drift@stud.ntnu.no\n"); return 0; } int usage_editperm() { printf("Usage: %s editperm [DATABASE]\n", program_name); printf("Edit permissions for the DATABASE. Running this command will\n"); printf("spawn the editor stored in the $EDITOR environment variable.\n"); printf("(pico will be used if the variable is unset)\n"); printf("\n"); printf("The file should contain one line per user, starting with the\n"); printf("username and followed by ten Y/N-values seperated by whitespace.\n"); printf("Lines starting with # are ignored.\n"); printf("\n"); printf("The Y/N-values corresponds to the following mysql privileges:\n"); printf(" Select - Enables use of SELECT\n"); printf(" Insert - Enables use of INSERT\n"); printf(" Update - Enables use of UPDATE\n"); printf(" Delete - Enables use of DELETE\n"); printf(" Create - Enables use of CREATE TABLE\n"); printf(" Drop - Enables use of DROP TABLE\n"); printf(" Alter - Enables use of ALTER TABLE\n"); printf(" Index - Enables use of CREATE INDEX and DROP INDEX\n"); printf(" Temp - Enables use of CREATE TEMPORARY TABLE\n"); printf(" Lock - Enables use of LOCK TABLE\n"); printf("\n"); printf("Report bugs to drift@stud.ntnu.no\n"); return 0; } int create(MYSQL *pmysql, char *db) { // hvis man forsøker å dra mysql_create_db() på en database som // allerede finnes, så henger bare hele sql-kallet. Vi må derfor // forsøke å selecte databasen først. Ettersom man alltid er root, // går jo dette vanligvis bra. // finner ut om denne finnes fra før. if (!mysql_select_db(pmysql, db)) { return dberror(pmysql, "Database '%s' already exists.", db); } mysql_select_db(pmysql, "mysql"); // oppretter databasen. char query[1024]; sprintf(query, "create database `%s`", db); if (mysql_query(pmysql, query)) return dberror(pmysql, "Cannot create database '%s'.", db); fprintf(stderr, "Database '%s' created.\n", db); return 0; } int drop(MYSQL *pmysql, char *db) { char query[1024]; sprintf(query, "delete from db where db = '%s'", db); if (mysql_query(pmysql, query)) dberror(pmysql, "Failed to delete permissions for database '%s'.", db); if (mysql_select_db(pmysql, db)) { dberror(pmysql, "Database '%s' doesn't exists.", db); return 0; } mysql_select_db(pmysql, "mysql"); sprintf(query, "drop database `%s`", db); if (mysql_query(pmysql, query)) return dberror(pmysql, "Cannot drop database '%s'.", db); fprintf(stderr, "Database '%s' dropped.\n", db); return 0; } /* return a list of the user's databases */ char ** list(MYSQL *pmysql) { char *wild; char **usr_groups, **cp; /* holds all group names this dude is a member of [tlan]*/ MYSQL_RES *res; int rows, numgroups, numgroupdbs; MYSQL_ROW row; char **dblist; int i, counter; struct passwd *p; // variabler som brukes til å escape gruppenavnet til mysqlvennlig format. char escaped_user[64]; char *cp_kopi; p = getpwuid(getuid()); if (!p) { dberror(NULL, "Failed to lookup your UNIX username."); exit(1); } dblist = malloc(2 * sizeof(char*)); /* one for username (if used), rest is done below */ numgroupdbs = 0; counter = 0; usr_groups = get_group_names(&numgroups); cp = usr_groups; while (*cp) { // itererer over alle grupper en person er med i if (*cp == NULL) break; #ifdef DEBUG printf("cp er %s\n", *cp); #endif escaped_user[0] = '\0'; cp_kopi=*cp; // itererer over bokstavene i gruppenavn, og escaper spesialtegn. for(i=0; i<=strlen(cp_kopi); i++) { // hvis % _ , så skriv \ og så tegn if ((cp_kopi[i] == '_') || (cp_kopi[i] == '%')) { strcat(escaped_user, "\\"); } escaped_user[strlen(escaped_user) + 1] = '\0'; escaped_user[strlen(escaped_user)] = cp_kopi[i]; } // for wild = malloc(strlen(escaped_user)+4); sprintf(wild, "%s\\_%%", escaped_user); #ifdef DEBUG printf("dbadm: wildcard: '%s'\n", wild); #endif res = mysql_list_dbs(pmysql, wild); rows = mysql_num_rows(res); if (rows > 0) { numgroupdbs += rows; dblist = realloc(dblist, (numgroupdbs+2) * sizeof(char *)); for (i = 0; i < rows; i++) { if ((row = mysql_fetch_row(res))) { dblist[counter++] = strdup(row[0]); } } } free(wild); free(res); free(cp_kopi); cp++; } wild = malloc(strlen(p->pw_name) + 4); sprintf(wild, "%s\\_%%", p->pw_name); #ifdef DEBUG printf("dbadm: wildcard: '%s'\n", wild); #endif res = mysql_list_dbs(pmysql, wild); rows = mysql_num_rows(res); dblist = realloc(dblist, (numgroupdbs+rows+2) * sizeof(char *)); if (!dblist) { dberror(NULL, "Out of memory.\n"); free(wild); return NULL; } for (i = 0; i < rows; i++) { if ((row = mysql_fetch_row(res))) { dblist[counter++] = strdup(row[0]); } } res = mysql_list_dbs(pmysql, p->pw_name); rows = mysql_num_rows(res); if (rows == 1) dblist[counter++] = strdup(p->pw_name); dblist[counter] = NULL; free(wild); free(usr_groups); return dblist; } int writeperm(FILE *f, MYSQL *pmysql, const char *db) { char query[1024]; MYSQL_RES *res; int rows, i; MYSQL_ROW row; sprintf(query, "select user,select_priv,insert_priv,update_priv," "delete_priv,create_priv,drop_priv,alter_priv,index_priv," "create_tmp_table_priv,lock_tables_priv from db where db='%s'", db); if (mysql_query(pmysql, query)) return dberror(pmysql, "Query for permissions failed."); res = mysql_store_result(pmysql); rows = mysql_num_rows(res); fprintf(f, "# User " "Select Insert Update Delete Create Drop Alter Index " " Temp Lock\n"); fprintf(f, "# ---------------- " "------ ------ ------ ------ ------ ---- ----- ----- " " ---- ----\n"); if (rows == 0) fprintf(f, "# (no permissions currently granted to any users)\n"); else { for (i = 0; i < rows; i++) { row = mysql_fetch_row(res); fprintf(f, " %-16s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %s\n", row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10]); } } free(res); return 0; } /* show information about the database */ int show(MYSQL *pmysql, const char *db) { printf("Database '%s':\n", db); writeperm(stdout, pmysql, db); return 0; } #define MAX_GRANTS 1024 int editperm(MYSQL *pmysql, const char *db) { char fn[] = "/tmp/mysql-dbadm.tmp.XXXXXX"; FILE *f; char *editor; char cmd[1024]; /* shell command for editing the file */ char line[1024]; /* buffer to hold one line */ char *cp; /* used to interate through a line */ char *user, *select_priv, *insert_priv, *update_priv, *delete_priv, *create_priv, *drop_priv, *alter_priv, *index_priv, *create_tmp_table_priv, *lock_tables_priv; char query[1024]; /* used to build a query */ char *queries[MAX_GRANTS]; /* insert queries */ int lines; /* number of grant lines processed */ int i; /* iterate through lines[] */ mkstemp(fn); if (strcmp(fn, "") == 0) return dberror(NULL, "Cannot create a unique temporary file name."); f = fopen(fn, "w"); if (f == NULL) return dberror(NULL, "Failed to open temporary file %s.", fn); writeperm(f, pmysql, db); fclose(f); editor = getenv("EDITOR"); if (!editor) editor = "pico"; /* OK since editor won't be freed */ strcpy(cmd, editor); strcat(cmd, " "); strcat(cmd, fn); if (system(cmd) == -1) { dberror(NULL, "Failed to execute '%s'\n", cmd); perror("system"); return 1; } /* parse */ f = fopen(fn, "r"); lines = 0; while (fgets(line, sizeof(line), f)) { cp = strchr(line, '\n'); if (cp) *cp = '\0'; cp = line; while (*cp && ((*cp == ' ') || (*cp == '\t'))) cp++; if (*cp == '\0') continue; if (*cp == '#') continue; if (*cp == '\n') continue; #define STRTOK_WHITESPACE(res, start) \ { if (!(res = strtok_whitespace(start))) continue; } STRTOK_WHITESPACE(user, cp); if (strlen(user) < 1) return dberror(NULL, "Invalid user '%s' in grant line %d.", user, lines + 1); if (strcmp(user, "%") == 0) *user = '\0'; /* ugly, but it works... */ #define CHECK_PRIV(PRIV) \ if (!valid_priv(PRIV)) return dberror(NULL, "Invalid value '%s' " \ "in grant line %d.\nMake sure you fill in a value (Y or N) for " \ "all ten privileges.\nRun '%s --help-editerm' for more " \ "information.\nNo permissions have been set.", PRIV, lines + 1, \ program_name) STRTOK_WHITESPACE(select_priv, NULL); CHECK_PRIV(select_priv); STRTOK_WHITESPACE(insert_priv, NULL); CHECK_PRIV(insert_priv); STRTOK_WHITESPACE(update_priv, NULL); CHECK_PRIV(update_priv); STRTOK_WHITESPACE(delete_priv, NULL); CHECK_PRIV(delete_priv); STRTOK_WHITESPACE(create_priv, NULL); CHECK_PRIV(create_priv); STRTOK_WHITESPACE(drop_priv, NULL); CHECK_PRIV(drop_priv); STRTOK_WHITESPACE(alter_priv, NULL); CHECK_PRIV(alter_priv); STRTOK_WHITESPACE(index_priv, NULL); CHECK_PRIV(index_priv); STRTOK_WHITESPACE(create_tmp_table_priv, NULL); CHECK_PRIV(create_tmp_table_priv); STRTOK_WHITESPACE(lock_tables_priv, NULL); CHECK_PRIV(lock_tables_priv); #undef STRTOK_WHITESPACE #undef CHECK_PRIV sprintf(query, "insert into db (host, db, user, select_priv, insert_priv, " "update_priv, delete_priv, create_priv, drop_priv, alter_priv, index_priv, " "create_tmp_table_priv, lock_tables_priv) values " "('%%', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", db, user, select_priv, insert_priv, update_priv, delete_priv, create_priv, drop_priv, alter_priv, index_priv, create_tmp_table_priv, lock_tables_priv); queries[lines] = strdup(query); lines++; if (lines >= MAX_GRANTS) { dberror(NULL, "Warning: Maximum of %d grants reached.\n", MAX_GRANTS); continue; } } /* while fgets ... */ unlink(fn); fclose(f); /* now that we have checked the input for errors, we can safely delete the old grants from the database and insert the new ones. */ sprintf(query, "delete from db where db='%s'", db); if (mysql_query(pmysql, query)) dberror(pmysql, "Failed to delete old grants for '%s'.", db); for (i = 0; i < lines; i++) { #ifdef DEBUG puts(queries[i]); putchar('\n'); #endif if (mysql_query(pmysql, queries[i])) dberror(pmysql, "Failed to insert grant line %d.", i + 1); } return 0; } int main(int argc, char *argv[]) { int i; enum { c_create, c_drop, c_editperm, c_show } command; MYSQL mysql; mysql_init(&mysql); char **dblist, **p; char *db; program_name = argv[0]; for (i = 1; i < argc; i++) if (strcmp(argv[i], "--help") == 0) return usage(); for (i = 1; i < argc; i++) if (strcmp(argv[i], "--help-editperm") == 0) return usage_editperm(); for (i = 1; i < argc; i++) if (strcmp(argv[i], "--version") == 0) return version(); if (argc < 2) return wrong_use(NULL); #ifdef DEBUG printf("NB NB NB: denne versjonen av programmet er kompilert med -DDEBUG, og\n"); printf("kan komme til å skrive ut ekstra informasjon. Dette er ikke farlig,\n"); printf("og programmet bør virke som vanlig.\n"); #endif /* check that the supplied command is valid */ if (strcmp(argv[1], "create") == 0) command = c_create; else if (strcmp(argv[1], "drop") == 0) command = c_drop; else if (strcmp(argv[1], "editperm") == 0) command = c_editperm; else if (strcmp(argv[1], "show") == 0) command = c_show; else return wrong_use("unrecognized command"); /* XXX */ /* all other than show requires at lease one DATABASE argument. */ if ((command != c_show) && (argc < 3)) return wrong_use(NULL); read_config_file(); /* connect to the database server and select the mysql database */ if (!mysql_real_connect(&mysql, db_server, db_user, db_passwd, db_name, 0, NULL, 0)) return dberror(&mysql, "Cannot connect to database server '%s'.", db_server); if (mysql_select_db(&mysql, db_name)) return dberror(&mysql, "Cannot select database '%s'.", db_name); if ((command == c_show) && (argc == 2)) { dblist = list(&mysql); p = dblist; while (*p) { show(&mysql, *p); free(*p); p++; } free(*p); free(dblist); } else { db = malloc(64); /* for each supplied database name, perform the requested action */ for (i = 2; i < argc; i++) { // HE HE strncpy(db, argv[i], 32); db[32] = '\0'; if (! (owner(db) || member(db))) { if (command == c_create) dberror(NULL,"Unable to create mysql-database '%s'.\n" "A mysql-database must start with either '%s_' or " "'groupname_', where groupname is a unix group you are a " "member of. Type \"groups\" to see which groups you are a " "member of.\n", db, getpwuid(getuid())->pw_name); else dberror(NULL, "You are not in charge of mysql-database: '%s'. Skipping.", db); continue; } switch (command) { case c_create: // We only check newly created databases. Many old ("unclean") databases are still in use. if(dbname_isclean(db)) { create(&mysql, db); } else { dberror(NULL, "Database name '%s' contains invalid characters.\n" "Only A-Z, a-z, 0-9, _ (underscore) and - (dash) permitted. Skipping.", db); } break; case c_drop: if(dbname_isclean(db)) { drop(&mysql, db); } else { dberror(NULL, "Database name '%s' contains invalid characters.\n" "Only A-Z, a-z, 0-9, _ (underscore) and - (dash) permitted. Skipping.", db); } break; case c_editperm: if(dbname_isclean(db)) { editperm(&mysql, db); } else { dberror(NULL, "Database name '%s' contains invalid characters.\n" "Only A-Z, a-z, 0-9, _ (underscore) and - (dash) permitted. Skipping.", db); } break; case c_show: if(dbname_isclean(db)) { show(&mysql, db); } else { dberror(NULL, "Database name '%s' contains invalid characters.\n" "Only A-Z, a-z, 0-9, _ (underscore) and - (dash) permitted. Skipping.", db); } break; default: return dberror(NULL, "This point should never be reached!"); } } // for free(db); } // else mysql_reload(&mysql); mysql_close(&mysql); return 0; }