/* Should we allow a given command? */ bool u_ratelimit_allow(u_user *user, u_ratelimit_cmd_t *deduct, const char *cmd) { time_t lastrate, numtokens; if ((strcasecmp(cmd, "WHO") == 0) && (user->limit.whotokens > 0)) { /* Compensate for WHO */ u_ratelimit_who_deduct(user); return true; } lastrate = NOW.tv_sec - user->limit.last; numtokens = lastrate / REFILLPERIOD; /* Update the last executed time */ user->limit.last = NOW.tv_sec; /* Grant tokens if needed */ user->limit.tokens += numtokens; if (user->limit.tokens > FLOODTOKENS) user->limit.tokens = FLOODTOKENS; /* Subtract tokens */ if ((user->limit.tokens <= deduct->deduction) || (user->limit.tokens - deduct->deduction) == 0) { user->limit.tokens = 0; u_log(LG_WARN, "User %U flooding!", user); /* XXX do a kill */ return false; } return true; }
static int mode_user(u_sourceinfo *si, char *s) { u_modes m; u_mode_buf_stack stack; if (!s || !*s) { u_src_num(si, RPL_UMODEIS, u_user_modes(si->u)); return 0; } m.ctx = &umodes; m.stacker = &u_mode_buf_stacker; m.setter = si; m.target = si->u; m.access = si; m.flags = SRC_IS_LOCAL_USER(si) ? 0 : MODE_FORCE_ALL; m.stack = &stack; u_mode_process(&m, 1, &s); if (stack.on != -1) { if (IS_LOCAL_USER(si->u)) { u_link_f(si->link, ":%U MODE %U %s%s", si->u, si->u, stack.cbuf, stack.dbuf); } u_sendto_servers(si->link, ":%U MODE %U %s%s", si->u, si->u, stack.cbuf, stack.dbuf); } u_log(LG_VERBOSE, "%U now has user mode %s", si->u, u_user_modes(si->u)); return 0; }
static int c_s_tb(u_sourceinfo *si, u_msg *msg) { char *chan = msg->argv[0]; int ts; u_chan *c; msg->propagate = CMD_DO_BROADCAST; if (!(c = u_chan_get(chan))) { u_log(LG_WARN, "%I sent TB for nonexistent %s", si, chan); return 0; } ts = atoi(msg->argv[1]); /* TODO: TS checking */ c->topic_time = ts; if (msg->argc > 3) { u_strlcpy(c->topic_setter, msg->argv[2], MAXNICKLEN+1); } else { snf(FMT_USER, c->topic_setter, MAXNICKLEN+1, "%I", si); } u_strlcpy(c->topic, msg->argv[msg->argc - 1], MAXTOPICLEN+1); u_sendto_chan(c, NULL, ST_USERS, ":%I TOPIC %C :%s", si, c, c->topic); return 0; }
static void cmode_stacker_send_list(u_modes *m) { mowgli_list_t *list; if (!m->setter->u) { u_log(LG_WARN, "Tried to send list to non-user"); return; } if (!(list = m->ctx->get_list(m, m->info))) { u_log(LG_SEVERE, "Can't send a list we don't have!!"); return; } u_chan_send_list(m->target, m->setter->u, list); }
static int try_local_join_chan(u_sourceinfo *si, char *chan, char *key) { u_chan *c, *fwd; u_chanuser *cu; bool created; int num; char *modes; /* verify entry */ if (!(c = u_chan_get_or_create(chan))) return u_user_num(si->u, ERR_NOSUCHCHANNEL, chan); created = c->ts == NOW.tv_sec; /* is this ok? */ if ((cu = u_chan_user_find(c, si->u)) != NULL) return 0; if ((num = u_entry_blocked(c, si->u, key)) != 0) { fwd = u_find_forward(c, si->u, key); if (fwd == NULL || u_chan_user_find(fwd, si->u)) return u_user_num(si->u, num, c); c = fwd; } /* perform join */ cu = u_chan_user_add(c, si->u); u_del_invite(c, si->u); if (created) cu->flags |= CU_PFX_OP; /* send messages */ u_sendto_chan(c, NULL, ST_USERS, ":%H JOIN %C", si->u, c); modes = u_chan_modes(c, 1); if (created) { u_log(LG_VERBOSE, "Channel %C %s created by %U", c, modes, si->u); u_conn_f(si->link, ":%S MODE %C %s", &me, c, modes); } if (!(c->flags & CHAN_LOCAL)) { if (c->members->size == 1) { u_sendto_servers(NULL, ":%S SJOIN %u %C %s :%s%U", &me, c->ts, c, modes, (cu->flags & CU_PFX_OP) ? "@" : "", si->u); } else { u_sendto_servers(NULL, ":%U JOIN %u %C +", si->u, c->ts, c); } } /* Credit them */ u_ratelimit_who_credit(si->u); u_chan_send_topic(c, si->u); u_chan_send_names(c, si->u); return 0; }
void u_perror_real(const char *func, const char *s) { int x = errno; char *error = strerror(x); u_log(LG_ERROR, "%s: %s: %s", func, s, error); }
u_link_origin *u_link_origin_create_from_fd(mowgli_eventloop_t *ev, int fd) { const char *operation; u_link_origin *origin = NULL; /* Set the fd to be close-on-exec. All listening sockets will be closed when * we upgrade. This may cause some slight disturbance for users currently * connecting, but this is acceptable. */ operation = "set close-on-exec"; if (set_cloexec(fd) < 0) goto error; u_log(LG_DEBUG, "u_link_origin_create_from_fd: %d", fd); origin = malloc(sizeof(*origin)); operation = "create pollable"; if (!(origin->poll = mowgli_pollable_create(ev, fd, origin))) { errno = -EINVAL; /* XXX */ goto error; } mowgli_node_add(origin, &origin->n, &all_origins); mowgli_pollable_setselect(ev, origin->poll, MOWGLI_EVENTLOOP_IO_READ, accept_ready); return origin; error: u_perror(operation); free(origin); return NULL; }
/* Serialization * ------------- */ mowgli_json_t *u_link_to_json(u_link *link) { mowgli_json_t *jl; if (!link) return NULL; jl = mowgli_json_create_object(); json_oseti (jl, "flags", link->flags); json_oseti (jl, "type", link->type); json_osets (jl, "pass", link->pass); json_oseti (jl, "sendq", link->sendq); json_oseto (jl, "ck_sendto", u_cookie_to_json(&link->ck_sendto)); json_oseto (jl, "conn", u_conn_to_json(link->conn)); json_osetb64(jl, "ibuf", link->ibuf + link->ibufskip, link->ibuflen - link->ibufskip); switch (link->type) { case LINK_USER: /* we can figure this out when we restore */ break; case LINK_SERVER: json_osets (jl, "server_link_block_name", link->conf.link->name); break; default: u_log(LG_SEVERE, "unexpected link type %d", link->type); abort(); } return jl; }
static int c_r_tmode(u_sourceinfo *si, u_msg *msg) { char *target = msg->argv[1]; int parc; char **parv; u_chan *c; u_modes m; struct cmode_stacker_private stack; if (!(c = u_chan_get(target))) { return u_log(LG_ERROR, "%G tried to TMODE nonexistent %s", si->source, target); } if (atoi(msg->argv[0]) > c->ts) return 0; parc = msg->argc - 2; parv = msg->argv + 2; m.ctx = &cmodes; m.stacker = &cmode_stacker; m.setter = si; m.target = c; m.access = &me; m.stack = &stack; u_mode_process(&m, parc, parv); return 0; }
static u_chanuser *do_remote_join_chan(u_user *u, u_chan *c) { u_chanuser *cu; if (IS_LOCAL_USER(u)) u_log(LG_WARN, "do_remote_join_chan on local user %U", u); if ((cu = u_chan_user_find(c, u))) { u_log(LG_WARN, "Already have chanuser %U/%C; ignoring", u, c); } else if (!(cu = u_chan_user_add(c, u))) { u_log(LG_ERROR, "Could not create chanuser %U/%C", u, c); return NULL; } u_sendto_chan(c, NULL, ST_USERS, ":%H JOIN :%C", u, c); return cu; }
void u_udb_row_start(u_udb *db, char *name) { if (db->reading) { u_log(LG_ERROR, "Tried to start row while reading database!"); return; } db->sz = snf(FMT_LOG, db->line, UDB_LINE_SIZE, "%s", name); }
static void on_cleanup(u_conn *conn) { u_log(LG_VERBOSE, "link: being cleaned up"); if (conn->priv) { link_destroy(conn->priv); conn->priv = NULL; } }
static void on_connect_finish(u_conn *conn, int err) { u_link *link = conn->priv; u_link_block *block = link->conf.link; u_log(LG_VERBOSE, "link: connect finished"); u_server_burst_1(link, block); }
static void *conf_end(void *unused, void *unused2) { if (all_origins.count != 0) return NULL; u_log(LG_WARN, "No listeners! Opening one on 6667"); u_link_origin_create(base_ev, 6667); return NULL; }
int u_log_write_ex(int fac, int lev, int flags, int err, const char* file, int line, const char *func, const char* fmt, ...) { va_list ap; err_type savederr; int rc; char msg[U_MAX_LOG_LENGTH], strerr[STRERR_BUFSZ], errmsg[STRERR_BUFSZ]; save_errno(savederr); /* build the message to send to the log system */ va_start(ap, fmt); rc = vsnprintf(msg, U_MAX_LOG_LENGTH, fmt, ap); va_end(ap); if(rc >= U_MAX_LOG_LENGTH) goto err; /* message too long */ /* init empty strings */ errmsg[0] = strerr[0] = 0; if(err) { u_strerror_r(err, strerr, sizeof(strerr)); snprintf(errmsg, sizeof(errmsg), "[errno: %d, %s]", err, strerr); errmsg[sizeof(errmsg) - 1] = 0; /* paranoid set */ } /* ok, send the msg to the logger */ if(flags & LOG_WRITE_FLAG_CTX) u_log(fac, lev, "[%s][%d:%s:%d:%s] %s %s", u_log_label(lev), getpid(), file, line, func, msg, errmsg); else u_log(fac, lev, "[%s][%d:::] %s %s", u_log_label(lev), getpid(), msg, errmsg); restore_errno(savederr); return 0; err: restore_errno(savederr); return ~0; }
static void conf_listen_port(mowgli_config_file_t *cf, mowgli_config_file_entry_t *ce) { ushort low, hi; char buf[512]; char *s, *lows, *his; mowgli_strlcpy(buf, ce->vardata, sizeof buf); lows = buf; his = NULL; if ((s = strstr(buf, "..")) || (s = strstr(buf, "-"))) { *s++ = '\0'; while (*s && !isdigit(*s)) s++; his = s; } low = atoi(lows); hi = (his && *his) ? atoi(his) : low; if (low == 0 || hi == 0) { u_log(LG_ERROR, "%s: invalid listen range string", ce->vardata); return; } if (hi < low) { u_log(LG_ERROR, "%u-%u: invalid listen range", low, hi); return; } if (hi - low > 20) { u_log(LG_ERROR, "%u-%u: listener range too large", low, hi); return; } for (; low <= hi; low++) { u_log(LG_DEBUG, "Listening on %u", low); u_link_origin_create(base_ev, low); } }
static void notice(u_user *u, const char *fmt, ...) { char buf[512]; va_list va; va_start(va, fmt); vsnf(FMT_USER, buf, 512, fmt, va); va_end(va); if (u == NULL) { /* ?? */ u_log(LG_INFO, "notice: %s", buf); } else { u_link_f(u->link, ":%S NOTICE %U :%s", &me, u, buf); } }
char *u_udb_get_s(u_udb *db) { char *s = db->buf; if (!db->reading) { u_log(LG_ERROR, "Tried to read word while writing database!"); return NULL; } while (*db->p != ' ' && *db->p) { if (*db->p == '\\') db->p++; *s++ = *db->p++; } *s = '\0'; return db->buf; }
u_link *u_link_connect(mowgli_eventloop_t *ev, u_link_block *block, const struct sockaddr *addr, socklen_t addrlen) { u_link *link = link_create(); u_conn *conn; if (!(conn = u_conn_connect(ev, &u_link_conn_ctx, link, 0, addr, addrlen))) { link_destroy(link); return NULL; } link->conf.link = block; u_log(LG_VERBOSE, "connecting to %s", conn->ip); return link; }
static void accept_ready(mowgli_eventloop_t *ev, mowgli_eventloop_io_t *io, mowgli_eventloop_io_dir_t dir, void *priv) { mowgli_eventloop_pollable_t *poll = mowgli_eventloop_io_pollable(io); u_conn *conn; u_link *link; sync_time(); link = link_create(); if (!(conn = u_conn_accept(ev, &u_link_conn_ctx, link, 0, poll->fd))) { link_destroy(link); /* TODO: close listener, maybe? */ return; } u_log(LG_VERBOSE, "new connection from %s", conn->ip); }
void u_link_vnum(u_link *link, const char *tgt, int num, va_list va) { char buf[4096]; char *fmt; if (!link) return; fmt = u_numeric_fmt[num]; if (fmt == NULL) { u_log(LG_SEVERE, "Attempted to use NULL numeric %d", num); return; } /* numerics are ALWAYS FMT_USER */ vsnf(FMT_USER, buf, 4096, fmt, va); u_link_f(link, ":%S %03d %s %s", &me, num, tgt, buf); }
int u_link_num(u_link *link, int num, ...) { va_list va; if (!link) return 0; va_start(va, num); switch (link->type) { case LINK_NONE: u_link_vnum(link, "*", num, va); break; case LINK_USER: u_user_vnum(link->priv, num, va); break; default: u_log(LG_SEVERE, "Can't use u_link_num on type %d!", link->type); } va_end(va); return 0; }
void u_link_vf(u_link *link, const char *fmt, va_list va) { uchar *buf; size_t sz; int type; if (!link) return; if (link->sendq > 0 && link->conn->sendq.size + 512 > link->sendq) { on_sendq_full(link->conn); return; } buf = u_conn_get_send_buffer(link->conn, 512); if (buf == NULL) { on_sendq_full(link->conn); return; } type = FMT_USER; if (link->type == LINK_SERVER) type = FMT_SERVER; sz = vsnf(type, (char*)buf, 510, fmt, va); buf[sz] = '\0'; u_log(LG_DEBUG, "[%G] <- %s", link, buf); buf[sz++] = '\r'; buf[sz++] = '\n'; u_conn_end_send_buffer(link->conn, sz); }
static void dispatch_lines(u_link *link) { uchar *buf; size_t buflen; uchar *s, *p; u_msg msg; buf = link->ibuf; buflen = link->ibuflen; /* i don't really ever comment my code, as it's mostly very straight forward and relatively self-documenting. but C string processing is typically very hairy stuff and it can be difficult to decipher the intent of a chunk of code, so i commented it --aji */ buf[buflen] = '\0'; while (buflen > 0) { /* check wait flags on every iteration, as line dispatch can affect this */ if (link->flags & U_LINK_WAIT) break; /* find the next \r and \n */ s = memchr(buf, '\r', buflen); p = memchr(buf, '\n', buflen); /* if no line endings in buffer, we're done */ if (!s && !p) break; /* if p is closer than s, then put p in s */ if (!s || (p && p < s)) s = p; /* s now contains the closest line ending */ /* delete all contiguous line endings at s */ for (p = s; *p == '\r' || *p == '\n'; p++) *p = '\0'; /* p now points at the start of the next line */ s = buf; buf = p; buflen = buflen - (p - s); /* s now points to the current line, and buf and buflen describe the rest of the buffer */ /* If executing this command causes an upgrade, u_cmd_invoke will not * return. Indicate the length of the current message to the dump function * so that it won't be serialized and re-execute after upgrade. */ link->ibufskip = (p - link->ibuf); /* dispatch the line */ u_log(LG_DEBUG, "[%G] -> %s", link, s); if (u_msg_parse(&msg, (char*)s) < 0) continue; u_cmd_invoke(link, &msg, (char*)s); } /* move remaining buffer contents to the start of the in buffer */ memmove(link->ibuf, link->ibuf + link->ibuflen - buflen, buflen); link->ibuflen = buflen; link->ibufskip = 0; }
u_link *u_link_from_json(mowgli_json_t *jl) { u_link *link; mowgli_json_t *jcookie, *jconn; mowgli_string_t *jpass, *jslinkname; ssize_t sz; link = link_create(); if (json_ogetu(jl, "flags", &link->flags) < 0) goto error; if (json_ogetu(jl, "type", &link->type) < 0) goto error; if (json_ogeti(jl, "sendq", &link->sendq) < 0) goto error; if ((sz = json_ogetb64(jl, "ibuf", link->ibuf, IBUFSIZE)) < 0) goto error; link->ibuflen = sz; link->ibuf[link->ibuflen] = '\0'; jpass = json_ogets(jl, "pass"); if (jpass) { link->pass = malloc(jpass->pos+1); memcpy(link->pass, jpass->str, jpass->pos); link->pass[jpass->pos] = '\0'; } jcookie = json_ogeto(jl, "ck_sendto"); if (!jcookie) goto error; if (u_cookie_from_json(jcookie, &link->ck_sendto) < 0) goto error; jconn = json_ogeto(jl, "conn"); if (!jconn) goto error; link->conn = u_conn_from_json(base_ev, &u_link_conn_ctx, link, jconn); if (!link->conn) goto error; link->conn->priv = link; /* This must run after the config has been loaded. */ switch (link->type) { case LINK_USER: /* If the user is post-registration, re-find his auth block. */ if (link->flags & U_LINK_REGISTERED) { /* XXX: Should this be in user.c? */ link->conf.auth = u_find_auth(link); if (!link->conf.auth) goto error; } break; case LINK_SERVER: jslinkname = json_ogets(jl, "server_link_block_name"); if (!jslinkname) goto error; /* mowgli_string is NULL-terminated. We needn't care about NULLs. */ link->conf.link = u_find_link(jslinkname->str); if (!link->conf.link) goto error; break; default: u_log(LG_SEVERE, "unexpected link type %d", link->type); abort(); } return link; error: if (link) { free(link->pass); free(link); } return NULL; }