ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/ircd-hybrid/trunk/src/auth.c
(Generate patch)

Comparing:
ircd-hybrid/trunk/src/s_auth.c (file contents), Revision 2916 by michael, Sat Jan 25 21:09:18 2014 UTC vs.
ircd-hybrid/trunk/src/auth.c (file contents), Revision 4815 by michael, Sat Nov 1 15:28:42 2014 UTC

# Line 15 | Line 15
15   *
16   *  You should have received a copy of the GNU General Public License
17   *  along with this program; if not, write to the Free Software
18 < *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
18 > *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19   *  USA
20   */
21  
22 < /*! \file s_auth.c
22 > /*! \file auth.c
23   * \brief Functions for querying a users ident.
24   * \version $Id$
25   */
# Line 39 | Line 39
39   #include "list.h"
40   #include "ircd_defs.h"
41   #include "fdlist.h"
42 < #include "s_auth.h"
42 > #include "auth.h"
43   #include "conf.h"
44   #include "client.h"
45   #include "event.h"
46 – #include "hook.h"
46   #include "irc_string.h"
47   #include "ircd.h"
48   #include "packet.h"
49 < #include "irc_res.h"
49 > #include "res.h"
50   #include "s_bsd.h"
51   #include "log.h"
52   #include "send.h"
53   #include "mempool.h"
54  
55  
56 < static const char *HeaderMessages[] =
56 > static const char *const HeaderMessages[] =
57   {
58 <  ":%s NOTICE AUTH :*** Looking up your hostname...",
59 <  ":%s NOTICE AUTH :*** Found your hostname",
60 <  ":%s NOTICE AUTH :*** Couldn't look up your hostname",
61 <  ":%s NOTICE AUTH :*** Checking Ident",
62 <  ":%s NOTICE AUTH :*** Got Ident response",
63 <  ":%s NOTICE AUTH :*** No Ident response",
64 <  ":%s NOTICE AUTH :*** Your forward and reverse DNS do not match, ignoring hostname.",
65 <  ":%s NOTICE AUTH :*** Your hostname is too long, ignoring hostname"
58 >  ":*** Looking up your hostname",
59 >  ":*** Found your hostname",
60 >  ":*** Couldn't look up your hostname",
61 >  ":*** Checking Ident",
62 >  ":*** Got Ident response",
63 >  ":*** No Ident response",
64 >  ":*** Your forward and reverse DNS do not match, ignoring hostname",
65 >  ":*** Your hostname is too long, ignoring hostname"
66   };
67  
68   enum
# Line 78 | Line 77 | enum
77    REPORT_HOST_TOOLONG
78   };
79  
80 < #define sendheader(c, i) sendto_one((c), HeaderMessages[(i)], me.name)
80 > #define sendheader(c, i) sendto_one_notice((c), &me, HeaderMessages[(i)])
81  
82 < static dlink_list auth_doing_list = { NULL, NULL, 0 };
82 > static dlink_list auth_pending_list;
83 > static void read_auth_reply(fde_t *, void *);
84 > static void auth_connect_callback(fde_t *, int, void *);
85  
85 – static EVH timeout_auth_queries_event;
86 –
87 – static PF read_auth_reply;
88 – static CNCB auth_connect_callback;
89 –
90 – /* auth_init
91 – *
92 – * Initialise the auth code
93 – */
94 – void
95 – auth_init(void)
96 – {
97 –  eventAddIsh("timeout_auth_queries_event", timeout_auth_queries_event, NULL, 1);
98 – }
86  
87   /*
88   * make_auth_request - allocate a new auth request
# Line 103 | Line 90 | auth_init(void)
90   static struct AuthRequest *
91   make_auth_request(struct Client *client)
92   {
93 <  struct AuthRequest *request = &client->localClient->auth;
93 >  struct AuthRequest *request = &client->connection->auth;
94  
95    memset(request, 0, sizeof(*request));
96  
# Line 126 | Line 113 | release_auth_client(struct AuthRequest *
113    if (IsDoingAuth(auth) || IsDNSPending(auth))
114      return;
115  
116 <  if (dlinkFind(&auth_doing_list, auth))
117 <    dlinkDelete(&auth->node, &auth_doing_list);
116 >  if (IsInAuth(auth))
117 >  {
118 >    dlinkDelete(&auth->node, &auth_pending_list);
119 >    ClearInAuth(auth);
120 >  }
121  
122    /*
123     * When a client has auth'ed, we want to start reading what it sends
124     * us. This is what read_packet() does.
125     *     -- adrian
126     */
127 <  client->localClient->allow_read = MAX_FLOOD;
128 <  comm_setflush(&client->localClient->fd, 1000, flood_recalc, client);
139 <
140 <  dlinkAdd(client, &client->node, &global_client_list);
127 >  client->connection->allow_read = MAX_FLOOD;
128 >  comm_setflush(&client->connection->fd, 1000, flood_recalc, client);
129  
130 <  client->localClient->since     = CurrentTime;
131 <  client->localClient->lasttime  = CurrentTime;
132 <  client->localClient->firsttime = CurrentTime;
130 >  client->connection->since     = CurrentTime;
131 >  client->connection->lasttime  = CurrentTime;
132 >  client->connection->firsttime = CurrentTime;
133    client->flags |= FLAGS_FINISHED_AUTH;
134  
135 <  read_packet(&client->localClient->fd, client);
135 >  read_packet(&client->connection->fd, client);
136   }
137  
138   /*
# Line 155 | Line 143 | release_auth_client(struct AuthRequest *
143   * of success of failure
144   */
145   static void
146 < auth_dns_callback(void *vptr, const struct irc_ssaddr *addr, const char *name)
146 > auth_dns_callback(void *vptr, const struct irc_ssaddr *addr, const char *name, size_t namelength)
147   {
148    struct AuthRequest *auth = vptr;
149  
150    ClearDNSPending(auth);
151  
152 <  if (name != NULL)
152 >  if (!EmptyString(name))
153    {
154      const struct sockaddr_in *v4, *v4dns;
167 – #ifdef IPV6
155      const struct sockaddr_in6 *v6, *v6dns;
169 – #endif
170 –    int good = 1;
156  
157 < #ifdef IPV6
173 <    if (auth->client->localClient->ip.ss.ss_family == AF_INET6)
157 >    if (auth->client->connection->ip.ss.ss_family == AF_INET6)
158      {
159 <      v6 = (const struct sockaddr_in6 *)&auth->client->localClient->ip;
159 >      v6 = (const struct sockaddr_in6 *)&auth->client->connection->ip;
160        v6dns = (const struct sockaddr_in6 *)addr;
161 +
162        if (memcmp(&v6->sin6_addr, &v6dns->sin6_addr, sizeof(struct in6_addr)) != 0)
163        {
164          sendheader(auth->client, REPORT_IP_MISMATCH);
165 <        good = 0;
165 >        release_auth_client(auth);
166 >        return;
167        }
168      }
169      else
184 – #endif
170      {
171 <      v4 = (const struct sockaddr_in *)&auth->client->localClient->ip;
171 >      v4 = (const struct sockaddr_in *)&auth->client->connection->ip;
172        v4dns = (const struct sockaddr_in *)addr;
173 <      if(v4->sin_addr.s_addr != v4dns->sin_addr.s_addr)
173 >
174 >      if (v4->sin_addr.s_addr != v4dns->sin_addr.s_addr)
175        {
176          sendheader(auth->client, REPORT_IP_MISMATCH);
177 <        good = 0;
177 >        release_auth_client(auth);
178 >        return;
179        }
180      }
181 <    if (good && strlen(name) <= HOSTLEN)
181 >
182 >    if (namelength > HOSTLEN)
183 >      sendheader(auth->client, REPORT_HOST_TOOLONG);
184 >    else
185      {
186 <      strlcpy(auth->client->host, name,
197 <              sizeof(auth->client->host));
186 >      strlcpy(auth->client->host, name, sizeof(auth->client->host));
187        sendheader(auth->client, REPORT_FIN_DNS);
188      }
200 –    else if (strlen(name) > HOSTLEN)
201 –      sendheader(auth->client, REPORT_HOST_TOOLONG);
189    }
190    else
191      sendheader(auth->client, REPORT_FAIL_DNS);
# Line 236 | Line 223 | start_auth_query(struct AuthRequest *aut
223   {
224    struct irc_ssaddr localaddr;
225    socklen_t locallen = sizeof(struct irc_ssaddr);
239 – #ifdef IPV6
226    struct sockaddr_in6 *v6;
241 – #else
242 –  struct sockaddr_in *v4;
243 – #endif
227  
228    /* open a socket of the same type as the client socket */
229 <  if (comm_open(&auth->fd, auth->client->localClient->ip.ss.ss_family,
229 >  if (comm_open(&auth->fd, auth->client->connection->ip.ss.ss_family,
230                  SOCK_STREAM, 0, "ident") == -1)
231    {
232      report_error(L_ALL, "creating auth stream socket %s:%s",
233                   get_client_name(auth->client, SHOW_IP), errno);
251 –    ilog(LOG_TYPE_IRCD, "Unable to create auth socket for %s",
252 –        get_client_name(auth->client, SHOW_IP));
234      ++ServerStats.is_abad;
235      return 0;
236    }
# Line 264 | Line 245 | start_auth_query(struct AuthRequest *aut
245     * and machines with multiple IP addresses are common now
246     */
247    memset(&localaddr, 0, locallen);
248 <  getsockname(auth->client->localClient->fd.fd, (struct sockaddr*)&localaddr,
248 >  getsockname(auth->client->connection->fd.fd, (struct sockaddr*)&localaddr,
249        &locallen);
250  
270 – #ifdef IPV6
251    remove_ipv6_mapping(&localaddr);
252    v6 = (struct sockaddr_in6 *)&localaddr;
253    v6->sin6_port = htons(0);
274 – #else
275 –  localaddr.ss_len = locallen;
276 –  v4 = (struct sockaddr_in *)&localaddr;
277 –  v4->sin_port = htons(0);
278 – #endif
254    localaddr.ss_port = htons(0);
255  
256 <  comm_connect_tcp(&auth->fd, auth->client->sockhost, 113,
256 >  comm_connect_tcp(&auth->fd, auth->client->sockhost, RFC1413_PORT,
257        (struct sockaddr *)&localaddr, localaddr.ss_len, auth_connect_callback,
258 <      auth, auth->client->localClient->ip.ss.ss_family,
258 >      auth, auth->client->connection->ip.ss.ss_family,
259        GlobalSetOptions.ident_timeout);
260    return 1; /* We suceed here for now */
261   }
262  
263   /*
289 – * GetValidIdent - parse ident query reply from identd server
290 – *
291 – * Inputs        - pointer to ident buf
292 – * Output        - NULL if no valid ident found, otherwise pointer to name
293 – * Side effects  -
294 – */
295 – /*
296 – * A few questions have been asked about this mess, obviously
297 – * it should have been commented better the first time.
298 – * The original idea was to remove all references to libc from ircd-hybrid.
299 – * Instead of having to write a replacement for sscanf(), I did a
300 – * rather gruseome parser here so we could remove this function call.
301 – * Note, that I had also removed a few floating point printfs as well (though
302 – * now we are still stuck with a few...)
303 – * Remember, we have a replacement ircd sprintf, we have bleeps fputs lib
304 – * it would have been nice to remove some unneeded code.
305 – * Oh well. If we don't remove libc stuff totally, then it would be
306 – * far cleaner to use sscanf()
307 – *
308 – * - Dianora
309 – */
310 – static char *
311 – GetValidIdent(char *buf)
312 – {
313 –  int   remp = 0;
314 –  int   locp = 0;
315 –  char* colon1Ptr;
316 –  char* colon2Ptr;
317 –  char* colon3Ptr;
318 –  char* commaPtr;
319 –  char* remotePortString;
320 –
321 –  /* All this to get rid of a sscanf() fun. */
322 –  remotePortString = buf;
323 –
324 –  if ((colon1Ptr = strchr(remotePortString,':')) == NULL)
325 –    return 0;
326 –  *colon1Ptr = '\0';
327 –  colon1Ptr++;
328 –
329 –  if ((colon2Ptr = strchr(colon1Ptr,':')) == NULL)
330 –    return 0;
331 –  *colon2Ptr = '\0';
332 –  colon2Ptr++;
333 –
334 –  if ((commaPtr = strchr(remotePortString, ',')) == NULL)
335 –    return 0;
336 –  *commaPtr = '\0';
337 –  commaPtr++;
338 –
339 –  if ((remp = atoi(remotePortString)) == 0)
340 –    return 0;
341 –
342 –  if ((locp = atoi(commaPtr)) == 0)
343 –    return 0;
344 –
345 –  /* look for USERID bordered by first pair of colons */
346 –  if (strstr(colon1Ptr, "USERID") == NULL)
347 –    return 0;
348 –
349 –  if ((colon3Ptr = strchr(colon2Ptr,':')) == NULL)
350 –    return 0;
351 –  *colon3Ptr = '\0';
352 –  colon3Ptr++;
353 –  return (colon3Ptr);
354 – }
355 –
356 – /*
264   * start_auth
265   *
266   * inputs       - pointer to client to auth
# Line 361 | Line 268 | GetValidIdent(char *buf)
268   * side effects - starts auth (identd) and dns queries for a client
269   */
270   void
271 < start_auth(struct Client *client)
271 > start_auth(struct Client *client_p)
272   {
273    struct AuthRequest *auth = NULL;
274  
275 <  assert(client != NULL);
275 >  assert(client_p);
276  
277 <  auth = make_auth_request(client);
278 <  dlinkAdd(auth, &auth->node, &auth_doing_list);
277 >  auth = make_auth_request(client_p);
278 >  SetInAuth(auth);
279 >  dlinkAddTail(auth, &auth->node, &auth_pending_list);
280  
281 <  sendheader(client, REPORT_DO_DNS);
281 >  sendheader(client_p, REPORT_DO_DNS);
282  
283    SetDNSPending(auth);
284  
285 <  if (ConfigFileEntry.disable_auth == 0)
285 >  if (ConfigGeneral.disable_auth == 0)
286    {
287      SetDoingAuth(auth);
288      start_auth_query(auth);
289    }
290  
291 <  gethost_byaddr(auth_dns_callback, auth, &client->localClient->ip);
291 >  gethost_byaddr(auth_dns_callback, auth, &client_p->connection->ip);
292   }
293  
294   /*
# Line 390 | Line 298 | start_auth(struct Client *client)
298   static void
299   timeout_auth_queries_event(void *notused)
300   {
301 <  dlink_node *ptr = NULL, *next_ptr = NULL;
301 >  dlink_node *node = NULL, *node_next = NULL;
302  
303 <  DLINK_FOREACH_SAFE(ptr, next_ptr, auth_doing_list.head)
303 >  DLINK_FOREACH_SAFE(node, node_next, auth_pending_list.head)
304    {
305 <    struct AuthRequest *auth = ptr->data;
305 >    struct AuthRequest *auth = node->data;
306  
307      if (auth->timeout > CurrentTime)
308 <      continue;
308 >      break;
309  
310      if (IsDoingAuth(auth))
311      {
# Line 414 | Line 322 | timeout_auth_queries_event(void *notused
322        sendheader(auth->client, REPORT_FAIL_DNS);
323      }
324  
417 –    ilog(LOG_TYPE_IRCD, "DNS/AUTH timeout %s",
418 –         get_client_name(auth->client, SHOW_IP));
325      release_auth_client(auth);
326    }
327   }
# Line 441 | Line 347 | auth_connect_callback(fde_t *fd, int err
347    socklen_t ulen = sizeof(struct irc_ssaddr);
348    socklen_t tlen = sizeof(struct irc_ssaddr);
349    uint16_t uport, tport;
444 – #ifdef IPV6
350    struct sockaddr_in6 *v6;
446 – #else
447 –  struct sockaddr_in *v4;
448 – #endif
351  
352    if (error != COMM_OK)
353    {
# Line 453 | Line 355 | auth_connect_callback(fde_t *fd, int err
355      return;
356    }
357  
358 <  if (getsockname(auth->client->localClient->fd.fd, (struct sockaddr *)&us,
359 <      &ulen) ||
458 <      getpeername(auth->client->localClient->fd.fd, (struct sockaddr *)&them,
459 <      &tlen))
358 >  if (getsockname(auth->client->connection->fd.fd, (struct sockaddr *)&us, &ulen) ||
359 >      getpeername(auth->client->connection->fd.fd, (struct sockaddr *)&them, &tlen))
360    {
361      ilog(LOG_TYPE_IRCD, "auth get{sock,peer}name error for %s",
362 <        get_client_name(auth->client, SHOW_IP));
362 >         get_client_name(auth->client, SHOW_IP));
363      auth_error(auth);
364      return;
365    }
366  
467 – #ifdef IPV6
367    v6 = (struct sockaddr_in6 *)&us;
368    uport = ntohs(v6->sin6_port);
369    v6 = (struct sockaddr_in6 *)&them;
370    tport = ntohs(v6->sin6_port);
371    remove_ipv6_mapping(&us);
372    remove_ipv6_mapping(&them);
474 – #else
475 –  v4 = (struct sockaddr_in *)&us;
476 –  uport = ntohs(v4->sin_port);
477 –  v4 = (struct sockaddr_in *)&them;
478 –  tport = ntohs(v4->sin_port);
479 –  us.ss_len = ulen;
480 –  them.ss_len = tlen;
481 – #endif
373  
374 <  snprintf(authbuf, sizeof(authbuf), "%u , %u\r\n", tport, uport);
374 >  snprintf(authbuf, sizeof(authbuf), "%u, %u\r\n", tport, uport);
375  
376    if (send(fd->fd, authbuf, strlen(authbuf), 0) == -1)
377    {
# Line 488 | Line 379 | auth_connect_callback(fde_t *fd, int err
379      return;
380    }
381  
382 <  read_auth_reply(&auth->fd, auth);
382 >  comm_setselect(fd, COMM_SELECT_READ, read_auth_reply, auth, 0);
383 > }
384 >
385 > /** Enum used to index ident reply fields in a human-readable way. */
386 > enum IdentReplyFields
387 > {
388 >  IDENT_PORT_NUMBERS,
389 >  IDENT_REPLY_TYPE,
390 >  IDENT_OS_TYPE,
391 >  IDENT_INFO,
392 >  USERID_TOKEN_COUNT
393 > };
394 >
395 > /** Parse an ident reply line and extract the userid from it.
396 > * \param reply The ident reply line.
397 > * \return The userid, or NULL on parse failure.
398 > */
399 > static const char *
400 > check_ident_reply(char *reply)
401 > {
402 >  char *token = NULL, *end = NULL;
403 >  char *vector[USERID_TOKEN_COUNT];
404 >  int count = token_vector(reply, ':', vector, USERID_TOKEN_COUNT);
405 >
406 >  if (USERID_TOKEN_COUNT != count)
407 >    return NULL;
408 >
409 >  /*
410 >   * Second token is the reply type
411 >   */
412 >  token = vector[IDENT_REPLY_TYPE];
413 >
414 >  if (EmptyString(token))
415 >    return NULL;
416 >
417 >  while (IsSpace(*token))
418 >    ++token;
419 >
420 >  if (strncmp(token, "USERID", 6))
421 >    return NULL;
422 >
423 >  /*
424 >   * Third token is the os type
425 >   */
426 >  token = vector[IDENT_OS_TYPE];
427 >
428 >  if (EmptyString(token))
429 >    return NULL;
430 >
431 >  while (IsSpace(*token))
432 >   ++token;
433 >
434 >  /*
435 >   * Unless "OTHER" is specified as the operating system type, the server
436 >   * is expected to return the "normal" user identification of the owner
437 >   * of this connection. "Normal" in this context may be taken to mean a
438 >   * string of characters which uniquely identifies the connection owner
439 >   * such as a user identifier assigned by the system administrator and
440 >   * used by such user as a mail identifier, or as the "user" part of a
441 >   * user/password pair used to gain access to system resources. When an
442 >   * operating system is specified (e.g., anything but "OTHER"), the user
443 >   * identifier is expected to be in a more or less immediately useful
444 >   * form - e.g., something that could be used as an argument to "finger"
445 >   * or as a mail address.
446 >   */
447 >  if (!strncmp(token, "OTHER", 5))
448 >    return NULL;
449 >
450 >  /*
451 >   * Fourth token is the username
452 >   */
453 >  token = vector[IDENT_INFO];
454 >
455 >  if (EmptyString(token))
456 >    return NULL;
457 >
458 >  while (IsSpace(*token))
459 >    ++token;
460 >
461 >  while (*token == '~' || *token == '^')
462 >    ++token;
463 >
464 >  /*
465 >   * Look for the end of the username, terminators are '\0, @, <SPACE>, :'
466 >   */
467 >  for (end = token; *end; ++end)
468 >    if (IsSpace(*end) || '@' == *end || ':' == *end)
469 >      break;
470 >  *end = '\0';
471 >
472 >  return token;
473   }
474  
475   /*
# Line 497 | Line 478 | auth_connect_callback(fde_t *fd, int err
478   * We only give it one shot, if the reply isn't good the first time
479   * fail the authentication entirely. --Bleep
480   */
500 – #define AUTH_BUFSIZ 128
501 –
481   static void
482   read_auth_reply(fde_t *fd, void *data)
483   {
484    struct AuthRequest *auth = data;
485 <  char *s = NULL;
486 <  char *t = NULL;
487 <  int len;
509 <  int count;
510 <  char buf[AUTH_BUFSIZ + 1]; /* buffer to read auth reply into */
511 <
512 <  /* Why?
513 <   * Well, recv() on many POSIX systems is a per-packet operation,
514 <   * and we do not necessarily want this, because on lowspec machines,
515 <   * the ident response may come back fragmented, thus resulting in an
516 <   * invalid ident response, even if the ident response was really OK.
517 <   *
518 <   * So PLEASE do not change this code to recv without being aware of the
519 <   * consequences.
520 <   *
521 <   *    --nenolod
522 <   */
523 <  len = read(fd->fd, buf, AUTH_BUFSIZ);
524 <
525 <  if (len < 0)
526 <  {
527 <    if (ignoreErrno(errno))
528 <      comm_setselect(fd, COMM_SELECT_READ, read_auth_reply, auth, 0);
529 <    else
530 <      auth_error(auth);
531 <    return;
532 <  }
485 >  const char *username = NULL;
486 >  ssize_t len = 0;
487 >  char buf[RFC1413_BUFSIZ + 1];
488  
489 <  if (len > 0)
489 >  if ((len = recv(fd->fd, buf, RFC1413_BUFSIZ, 0)) > 0)
490    {
491      buf[len] = '\0';
492 <
538 <    if ((s = GetValidIdent(buf)))
539 <    {
540 <      t = auth->client->username;
541 <
542 <      while (*s == '~' || *s == '^')
543 <        s++;
544 <
545 <      for (count = USERLEN; *s && count; s++)
546 <      {
547 <        if (*s == '@')
548 <          break;
549 <        if (!IsSpace(*s) && *s != ':' && *s != '[')
550 <        {
551 <          *t++ = *s;
552 <          count--;
553 <        }
554 <      }
555 <
556 <      *t = '\0';
557 <    }
492 >    username = check_ident_reply(buf);
493    }
494  
495    fd_close(fd);
496  
497    ClearAuth(auth);
498  
499 <  if (s == NULL)
499 >  if (EmptyString(username))
500    {
501      sendheader(auth->client, REPORT_FAIL_ID);
502      ++ServerStats.is_abad;
503    }
504    else
505    {
506 +    strlcpy(auth->client->username, username, sizeof(auth->client->username));
507      sendheader(auth->client, REPORT_FIN_ID);
508      ++ServerStats.is_asuc;
509      SetGotId(auth->client);
# Line 588 | Line 524 | delete_auth(struct AuthRequest *auth)
524    if (IsDoingAuth(auth))
525      fd_close(&auth->fd);
526  
527 <  if (dlinkFind(&auth_doing_list, auth))
528 <    dlinkDelete(&auth->node, &auth_doing_list);
527 >  if (IsInAuth(auth))
528 >  {
529 >    dlinkDelete(&auth->node, &auth_pending_list);
530 >    ClearInAuth(auth);
531 >  }
532 > }
533 >
534 > /* auth_init
535 > *
536 > * Initialise the auth code
537 > */
538 > void
539 > auth_init(void)
540 > {
541 >  static struct event timeout_auth_queries =
542 >  {
543 >    .name = "timeout_auth_queries_event",
544 >    .handler = timeout_auth_queries_event,
545 >    .when = 1
546 >  };
547 >
548 >  event_add(&timeout_auth_queries, NULL);
549   }

Diff Legend

– Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)