diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index f99ced2..8bed216 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -53,18 +53,21 @@ namespace network { uint8_t *netGetScreenshot(uint16_t w, uint16_t h, const uint8_t q, const bool dedup, uint32_t &len, uint8_t *staging); - uint8_t netAddUser(const char name[], const char pw[], const bool write, const bool owner); + uint8_t netAddUser(const char name[], const char pw[], + const bool read, const bool write, const bool owner); uint8_t netRemoveUser(const char name[]); uint8_t netUpdateUser(const char name[], const uint64_t mask, const char password[], - const bool write, const bool owner); + const bool read, const bool write, const bool owner); uint8_t netAddOrUpdateUser(const struct kasmpasswd_entry_t *entry); void netGetUsers(const char **ptr); void netGetBottleneckStats(char *buf, uint32_t len); void netGetFrameStats(char *buf, uint32_t len); + void netResetFrameStatsCall(); uint8_t netServerFrameStatsReady(); enum USER_ACTION { + NONE, WANT_FRAME_STATS_SERVERONLY, WANT_FRAME_STATS_ALL, WANT_FRAME_STATS_OWNER, diff --git a/common/network/GetAPIEnums.h b/common/network/GetAPIEnums.h index a651781..0bfce6b 100644 --- a/common/network/GetAPIEnums.h +++ b/common/network/GetAPIEnums.h @@ -24,6 +24,7 @@ enum USER_UPDATE_MASK { USER_UPDATE_WRITE_MASK = 1 << 0, USER_UPDATE_OWNER_MASK = 1 << 1, USER_UPDATE_PASSWORD_MASK = 1 << 2, + USER_UPDATE_READ_MASK = 1 << 3, }; #endif diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 0269609..405cbc5 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -264,7 +264,8 @@ uint8_t *GetAPIMessager::netGetScreenshot(uint16_t w, uint16_t h, return ret; } -uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const bool write, +uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], + const bool read, const bool write, const bool owner) { if (strlen(name) >= USERNAME_LEN) { vlog.error("Username too long"); @@ -289,6 +290,7 @@ uint8_t GetAPIMessager::netAddUser(const char name[], const char pw[], const boo act.data.password[PASSWORD_LEN - 1] = '\0'; act.data.owner = owner; act.data.write = write; + act.data.read = read; if (pthread_mutex_lock(&userMutex)) return 0; @@ -367,7 +369,7 @@ uint8_t GetAPIMessager::netRemoveUser(const char name[]) { uint8_t GetAPIMessager::netUpdateUser(const char name[], const uint64_t mask, const char password[], - const bool write, const bool owner) { + const bool read, const bool write, const bool owner) { if (strlen(name) >= USERNAME_LEN) { vlog.error("Username too long"); return 0; @@ -391,6 +393,8 @@ uint8_t GetAPIMessager::netUpdateUser(const char name[], const uint64_t mask, unsigned s; for (s = 0; s < set->num; s++) { if (!strcmp(set->entries[s].user, name)) { + if (mask & USER_UPDATE_READ_MASK) + set->entries[s].read = read; if (mask & USER_UPDATE_WRITE_MASK) set->entries[s].write = write; if (mask & USER_UPDATE_OWNER_MASK) @@ -487,8 +491,9 @@ void GetAPIMessager::netGetUsers(const char **outptr) { for (s = 0; s < set->num; s++) { JSON_escape(set->entries[s].user, escapeduser); - fprintf(f, " { \"user\": \"%s\", \"write\": %s, \"owner\": %s }", + fprintf(f, " { \"user\": \"%s\", \"read\": %s, \"write\": %s, \"owner\": %s }", escapeduser, + set->entries[s].read ? "true" : "false", set->entries[s].write ? "true" : "false", set->entries[s].owner ? "true" : "false"); @@ -687,6 +692,15 @@ out: pthread_mutex_unlock(&frameStatMutex); } +void GetAPIMessager::netResetFrameStatsCall() { + if (pthread_mutex_lock(&frameStatMutex)) + return; + + serverFrameStats.inprogress = 0; + + pthread_mutex_unlock(&frameStatMutex); +} + uint8_t GetAPIMessager::netRequestFrameStats(USER_ACTION what, const char *client) { // Return 1 for success action_data act; diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index af53e44..6353358 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -443,10 +443,10 @@ static uint8_t *screenshotCb(void *messager, uint16_t w, uint16_t h, const uint8 } static uint8_t adduserCb(void *messager, const char name[], const char pw[], - const uint8_t write, const uint8_t owner) + const uint8_t read, const uint8_t write, const uint8_t owner) { GetAPIMessager *msgr = (GetAPIMessager *) messager; - return msgr->netAddUser(name, pw, write, owner); + return msgr->netAddUser(name, pw, read, write, owner); } static uint8_t removeCb(void *messager, const char name[]) @@ -457,10 +457,10 @@ static uint8_t removeCb(void *messager, const char name[]) static uint8_t updateUserCb(void *messager, const char name[], const uint64_t mask, const char password[], - const uint8_t write, const uint8_t owner) + const uint8_t read, const uint8_t write, const uint8_t owner) { GetAPIMessager *msgr = (GetAPIMessager *) messager; - return msgr->netUpdateUser(name, mask, password, write, owner); + return msgr->netUpdateUser(name, mask, password, read, write, owner); } static uint8_t addOrUpdateUserCb(void *messager, const struct kasmpasswd_entry_t *entry) @@ -487,6 +487,12 @@ static void frameStatsCb(void *messager, char *buf, uint32_t len) msgr->netGetFrameStats(buf, len); } +static void resetFrameStatsCb(void *messager) +{ + GetAPIMessager *msgr = (GetAPIMessager *) messager; + msgr->netResetFrameStatsCall(); +} + static uint8_t requestFrameStatsNoneCb(void *messager) { GetAPIMessager *msgr = (GetAPIMessager *) messager; @@ -632,6 +638,7 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.getUsersCb = getUsersCb; settings.bottleneckStatsCb = bottleneckStatsCb; settings.frameStatsCb = frameStatsCb; + settings.resetFrameStatsCb = resetFrameStatsCb; settings.requestFrameStatsNoneCb = requestFrameStatsNoneCb; settings.requestFrameStatsOwnerCb = requestFrameStatsOwnerCb; diff --git a/common/network/jsonescape.c b/common/network/jsonescape.c index ce97629..4bb0ef8 100644 --- a/common/network/jsonescape.c +++ b/common/network/jsonescape.c @@ -150,14 +150,13 @@ struct kasmpasswd_t *parseJsonUsers(const char *data) { if (e->type & cJSON_True) entry->owner = 1; -/* } else field("read") { - start = end + 3; + } else field("read") { if (!(e->type & (cJSON_False | cJSON_True))) goto fail; if (e->type & cJSON_True) entry->read = 1; -*/ + } else { //printf("Unknown field '%.*s'\n", len, start); goto fail; diff --git a/common/network/websocket.c b/common/network/websocket.c index b136dda..92fe059 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -216,10 +216,10 @@ ws_ctx_t *ws_socket(ws_ctx_t *ctx, int socket) { return ctx; } -ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, char * certfile, char * keyfile) { +ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, const char * certfile, const char * keyfile) { int ret; char msg[1024]; - char * use_keyfile; + const char * use_keyfile; ws_socket(ctx, socket); if (keyfile && (keyfile[0] != '\0')) { @@ -292,7 +292,7 @@ void ws_socket_free(ws_ctx_t *ctx) { } } -int ws_b64_ntop(const unsigned char const * src, size_t srclen, char * dst, size_t dstlen) { +int ws_b64_ntop(const unsigned char * const src, size_t srclen, char * dst, size_t dstlen) { int len = 0; int total_len = 0; @@ -327,7 +327,7 @@ int ws_b64_ntop(const unsigned char const * src, size_t srclen, char * dst, size return len; } -int ws_b64_pton(const char const * src, unsigned char * dst, size_t dstlen) { +int ws_b64_pton(const char * const src, unsigned char * dst, size_t dstlen) { int len = 0; int total_len = 0; int pending = 0; @@ -724,14 +724,14 @@ int gen_md5(headers_t *headers, char *target) { static void gen_sha1(headers_t *headers, char *target) { SHA_CTX c; unsigned char hash[SHA_DIGEST_LENGTH]; - int r; + //int r; SHA1_Init(&c); SHA1_Update(&c, headers->key1, strlen(headers->key1)); SHA1_Update(&c, HYBI_GUID, 36); SHA1_Final(hash, &c); - r = ws_b64_ntop(hash, sizeof hash, target, HYBI10_ACCEPTHDRLEN); + /*r =*/ ws_b64_ntop(hash, sizeof hash, target, HYBI10_ACCEPTHDRLEN); //assert(r == HYBI10_ACCEPTHDRLEN - 1); } @@ -1021,7 +1021,8 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in) { goto nope; } - uint64_t mask = USER_UPDATE_WRITE_MASK | USER_UPDATE_OWNER_MASK; + uint64_t mask = USER_UPDATE_READ_MASK | USER_UPDATE_WRITE_MASK | + USER_UPDATE_OWNER_MASK; if (set->entries[s].password[0]) { struct crypt_data cdata; @@ -1035,6 +1036,7 @@ static uint8_t ownerapi_post(ws_ctx_t *ws_ctx, const char *in) { if (!settings.updateUserCb(settings.messager, set->entries[s].user, mask, set->entries[s].password, + set->entries[s].read, set->entries[s].write, set->entries[s].owner)) { wserr("Invalid params to update_user\n"); goto nope; @@ -1173,7 +1175,7 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { } } else entry("/api/create_user") { char decname[1024] = "", decpw[1024] = ""; - uint8_t write = 0, owner = 0; + uint8_t read = 0, write = 0, owner = 0; param = parse_get(args, "name", &len); if (len) { @@ -1195,6 +1197,12 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { strcpy(decpw, encrypted); } + param = parse_get(args, "read", &len); + if (len && isalpha(param[0])) { + if (!strncmp(param, "true", len)) + read = 1; + } + param = parse_get(args, "write", &len); if (len && isalpha(param[0])) { if (!strncmp(param, "true", len)) @@ -1210,7 +1218,7 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { if (!decname[0] || !decpw[0]) goto nope; - if (!settings.adduserCb(settings.messager, decname, decpw, write, owner)) { + if (!settings.adduserCb(settings.messager, decname, decpw, read, write, owner)) { wserr("Invalid params to create_user\n"); goto nope; } @@ -1267,6 +1275,14 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { goto nope; uint64_t mask = 0; + uint8_t myread = 0; + param = parse_get(args, "read", &len); + if (len && isalpha(param[0])) { + mask |= USER_UPDATE_READ_MASK; + if (!strncmp(param, "true", len)) + myread = 1; + } + uint8_t mywrite = 0; param = parse_get(args, "write", &len); if (len && isalpha(param[0])) { @@ -1283,7 +1299,8 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { myowner = 1; } - if (!settings.updateUserCb(settings.messager, decname, mask, "", mywrite, myowner)) { + if (!settings.updateUserCb(settings.messager, decname, mask, "", + myread, mywrite, myowner)) { wserr("Invalid params to update_user\n"); goto nope; } @@ -1370,14 +1387,25 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { goto nope; } - while (1) { - usleep(10 * 1000); - if (settings.serverFrameStatsReadyCb(settings.messager)) - break; + unsigned waits; + { + uint8_t failed = 1; + for (waits = 0; waits < 500; waits++) { // wait up to 10s + usleep(20 * 1000); + if (settings.serverFrameStatsReadyCb(settings.messager)) { + failed = 0; + break; + } + } + + if (failed) { + wserr("Main thread didn't respond, aborting (bug!)\n"); + settings.resetFrameStatsCb(settings.messager); + goto timeout; + } } if (waitfor) { - unsigned waits; for (waits = 0; waits < 20; waits++) { // wait up to 2s if (settings.getClientFrameStatsNumCb(settings.messager) >= waitfor) break; @@ -1403,6 +1431,7 @@ static uint8_t ownerapi(ws_ctx_t *ws_ctx, const char *in) { #undef entry return ret; + nope: sprintf(buf, "HTTP/1.1 400 Bad Request\r\n" "Server: KasmVNC/4.0\r\n" @@ -1412,6 +1441,16 @@ nope: "400 Bad Request"); ws_send(ws_ctx, buf, strlen(buf)); return 1; + +timeout: + sprintf(buf, "HTTP/1.1 503 Service Unavailable\r\n" + "Server: KasmVNC/4.0\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\n" + "\r\n" + "503 Service Unavailable"); + ws_send(ws_ctx, buf, strlen(buf)); + return 1; } ws_ctx_t *do_handshake(int sock, const char *ip) { @@ -1696,7 +1735,7 @@ out: } void *start_server(void *unused) { - int csock, pid; + int csock; struct sockaddr_in cli_addr; socklen_t clilen; @@ -1706,7 +1745,6 @@ void *start_server(void *unused) { while (1) { clilen = sizeof(cli_addr); pipe_error = 0; - pid = 0; do { csock = accept(settings.listen_sock, (struct sockaddr *) &cli_addr, diff --git a/common/network/websocket.h b/common/network/websocket.h index fc7e052..f825705 100644 --- a/common/network/websocket.h +++ b/common/network/websocket.h @@ -84,14 +84,15 @@ typedef struct { const uint8_t dedup, uint32_t *len, uint8_t *staging); uint8_t (*adduserCb)(void *messager, const char name[], const char pw[], - const uint8_t write, const uint8_t owner); + const uint8_t read, const uint8_t write, const uint8_t owner); uint8_t (*removeCb)(void *messager, const char name[]); uint8_t (*updateUserCb)(void *messager, const char name[], const uint64_t mask, const char password[], - const uint8_t write, const uint8_t owner); + const uint8_t read, const uint8_t write, const uint8_t owner); uint8_t (*addOrUpdateUserCb)(void *messager, const struct kasmpasswd_entry_t *entry); void (*bottleneckStatsCb)(void *messager, char *buf, uint32_t len); void (*frameStatsCb)(void *messager, char *buf, uint32_t len); + void (*resetFrameStatsCb)(void *messager); uint8_t (*requestFrameStatsNoneCb)(void *messager); uint8_t (*requestFrameStatsOwnerCb)(void *messager); diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 5567a96..1dde043 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -216,7 +216,7 @@ void SConnection::processSecurityMsg() bool done = ssecurity->processMsg(this); if (done) { state_ = RFBSTATE_QUERYING; - setAccessRights(ssecurity->getAccessRights()); + //setAccessRights(ssecurity->getAccessRights()); queryConnection(ssecurity->getUserName()); } } catch (AuthFailureException& e) { diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 8b5b3de..515fa9b 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -144,7 +144,6 @@ namespace rfb { static const AccessRights AccessDefault; // The default rights, INCLUDING FUTURE ONES static const AccessRights AccessNoQuery; // Connect without local user accepting static const AccessRights AccessFull; // All of the available AND FUTURE rights - virtual void setAccessRights(AccessRights ar) = 0; // Other methods diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index fa85a70..84e48e9 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -87,10 +87,16 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, user[at - peerEndpoint.buf] = '\0'; } - bool write, owner; - if (!getPerms(write, owner) || !write) { + bool read, write, owner; + if (!getPerms(read, write, owner)) { + accessRights &= ~(WRITER_PERMS | AccessView); + } + if (!write) { accessRights &= ~WRITER_PERMS; } + if (!read) { + accessRights &= ~AccessView; + } // Configure the socket setSocketTimeouts(); @@ -707,7 +713,13 @@ void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask, const bool { pointerEventTime = lastEventTime = time(0); server->lastUserInputTime = lastEventTime; - if (!(accessRights & AccessPtrEvents)) return; + if (!(accessRights & AccessPtrEvents)) { + // This particular event is lost, but it's a corner case - you removed write access + // from yourself, then added it back. The intended use is for multiple clients, + // where the leader removes and adds back access for others, not himself. + recheckPerms(); + return; + } if (!rfb::Server::acceptPointerEvents) return; if (!server->pointerClient || server->pointerClient == this) { pointerEventPos = pos; @@ -1105,11 +1117,12 @@ bool VNCSConnectionST::isShiftPressed() return false; } -bool VNCSConnectionST::getPerms(bool &write, bool &owner) const +bool VNCSConnectionST::getPerms(bool &read, bool &write, bool &owner) const { bool found = false; if (disablebasicauth) { // We're running without basicauth + read = true; write = true; return true; } @@ -1118,8 +1131,14 @@ bool VNCSConnectionST::getPerms(bool &write, bool &owner) const unsigned i; for (i = 0; i < set->num; i++) { if (!strcmp(set->entries[i].user, user)) { + read = set->entries[i].read; write = set->entries[i].write; owner = set->entries[i].owner; + + // Writer can always read + if (write) + read = true; + found = true; break; } @@ -1217,18 +1236,29 @@ void VNCSConnectionST::writeFramebufferUpdate() if (needsPermCheck) { needsPermCheck = false; - bool write, owner, ret; - ret = getPerms(write, owner); + bool read, write, owner, ret; + ret = getPerms(read, write, owner); if (!ret) { close("User was deleted"); return; - } else if (!write) { + } + + if (!write) { accessRights &= ~WRITER_PERMS; } else { accessRights |= WRITER_PERMS; } + + if (!read) { + accessRights &= ~AccessView; + } else { + accessRights |= AccessView; + } } + if (!(accessRights & AccessView)) + return; + // Updates often consists of many small writes, and in continuous // mode, we will also have small fence messages around the update. We // need to aggregate these in order to not clog up TCP's congestion @@ -1659,8 +1689,8 @@ bool VNCSConnectionST::checkOwnerConn() const std::list::const_iterator it; for (it = server->clients.begin(); it != server->clients.end(); it++) { - bool write, owner; - if ((*it)->getPerms(write, owner) && owner) + bool read, write, owner; + if ((*it)->getPerms(read, write, owner) && owner) return true; } diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 63ce49c..813281c 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -171,8 +171,8 @@ namespace rfb { virtual void handleFrameStats(rdr::U32 all, rdr::U32 render); bool is_owner() const { - bool write, owner; - if (getPerms(write, owner) && owner) + bool read, write, owner; + if (getPerms(read, write, owner) && owner) return true; return false; } @@ -227,19 +227,6 @@ namespace rfb { (AccessPtrEvents | AccessKeyEvents); } - // setAccessRights() allows a security package to limit the access rights - // of a VNCSConnectioST to the server. These access rights are applied - // such that the actual rights granted are the minimum of the server's - // default access settings and the connection's access settings. - virtual void setAccessRights(AccessRights ar) { - accessRights = ar; - - bool write, owner; - if (!getPerms(write, owner) || !write) - accessRights &= ~WRITER_PERMS; - needsPermCheck = false; - } - // Timer callbacks virtual bool handleTimeout(Timer* t); @@ -247,7 +234,7 @@ namespace rfb { bool isShiftPressed(); - bool getPerms(bool &write, bool &owner) const; + bool getPerms(bool &read, bool &write, bool &owner) const; bool checkOwnerConn() const; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 0511171..e66f956 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -807,6 +807,9 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager, const network::GetAPIMessager::action_data &act = apimessager->actionQueue[i]; switch (act.action) { + case network::GetAPIMessager::NONE: + slog.info("Empty request (bug!)"); + break; case network::GetAPIMessager::WANT_FRAME_STATS_SERVERONLY: trackingFrameStats = act.action; break; diff --git a/unix/kasmvncpasswd/kasmpasswd.c b/unix/kasmvncpasswd/kasmpasswd.c index bee2870..bc6bf15 100644 --- a/unix/kasmvncpasswd/kasmpasswd.c +++ b/unix/kasmvncpasswd/kasmpasswd.c @@ -60,6 +60,8 @@ struct kasmpasswd_t *readkasmpasswd(const char path[]) { strcpy(set->entries[cur].user, buf); strcpy(set->entries[cur].password, pw); + if (strchr(perms, 'r')) + set->entries[cur].read = 1; if (strchr(perms, 'w')) set->entries[cur].write = 1; if (strchr(perms, 'o')) @@ -90,22 +92,17 @@ void writekasmpasswd(const char path[], const struct kasmpasswd_t *set) { return; } - static const char * const perms[] = { - "", - "w", - "o", - "ow" - }; - unsigned i; for (i = 0; i < set->num; i++) { if (!set->entries[i].user[0]) continue; - fprintf(f, "%s:%s:%s\n", + fprintf(f, "%s:%s:%s%s%s\n", set->entries[i].user, set->entries[i].password, - perms[set->entries[i].owner * 2 + set->entries[i].write]); + set->entries[i].read ? "r" : "", + set->entries[i].write ? "w" : "", + set->entries[i].owner ? "o" : ""); } fsync(fileno(f)); diff --git a/unix/kasmvncpasswd/kasmpasswd.h b/unix/kasmvncpasswd/kasmpasswd.h index 81bde80..c6cbed0 100644 --- a/unix/kasmvncpasswd/kasmpasswd.h +++ b/unix/kasmvncpasswd/kasmpasswd.h @@ -8,6 +8,7 @@ extern "C" { struct kasmpasswd_entry_t { char user[32]; char password[128]; + unsigned char read : 1; unsigned char write : 1; unsigned char owner : 1; }; diff --git a/unix/kasmvncpasswd/kasmvncpasswd.c b/unix/kasmvncpasswd/kasmvncpasswd.c index cb893cc..2a7224c 100644 --- a/unix/kasmvncpasswd/kasmvncpasswd.c +++ b/unix/kasmvncpasswd/kasmvncpasswd.c @@ -34,6 +34,7 @@ static void usage(const char *prog) { fprintf(stderr, "Usage: %s -u username [-wnod] [file]\n" + "-r Read permission\n" "-w Write permission\n" "-o Owner\n" "-n Don't change password, change permissions only\n" @@ -118,10 +119,10 @@ int main(int argc, char** argv) { const char *fname = NULL; const char *user = NULL; - const char args[] = "u:wnod"; + const char args[] = "u:rwnod"; int opt; - unsigned char nopass = 0, writer = 0, owner = 0, deleting = 0; + unsigned char nopass = 0, reader = 0, writer = 0, owner = 0, deleting = 0; while ((opt = getopt(argc, argv, args)) != -1) { switch (opt) { @@ -135,6 +136,9 @@ int main(int argc, char** argv) case 'n': nopass = 1; break; + case 'r': + reader = 1; + break; case 'w': writer = 1; break; @@ -150,7 +154,7 @@ int main(int argc, char** argv) } } - if (deleting && (nopass || writer || owner)) + if (deleting && (nopass || reader || writer || owner)) usage(argv[0]); if (!user) @@ -175,6 +179,7 @@ int main(int argc, char** argv) if (nopass) { for (i = 0; i < set->num; i++) { if (!strcmp(set->entries[i].user, user)) { + set->entries[i].read = reader; set->entries[i].write = writer; set->entries[i].owner = owner; @@ -211,6 +216,7 @@ int main(int argc, char** argv) strcpy(set->entries[i].user, user); strcpy(set->entries[i].password, encrypted); + set->entries[i].read = reader; set->entries[i].write = writer; set->entries[i].owner = owner;